Русский
Русский
English
Статистика
Реклама

Reaction обработка результатов методов в Kotlin

Каждый, кто использовал Чистую архитектуру в разработке, сталкивался с проблемой передачи данных между слоями. Суть проблемы всегда одинакова: необходимо вернуть либо результат, либо ошибку. Представить это можно, например, так:

interface Reactiondata class Success(val data: String) : Reactiondata class Error(message: String) : Reaction

В зависимости от задачи, такие Reactionы могут быть самые разные, поэтому давайте объединим его в один класс, используя Generics и Sealed classы.

sealed class Reaction<out T> {   class Success<out T>(val data: T) : Reaction<T>()   class Error(val exception: Throwable) : Reaction<Nothing>()}

Разберем пример как это можно использовать

class MyViewModel : ViewModel {private val repository: Repositoryfun doSomething() {viewModelScope.launch(Dispatchers.IO) {val result = repository.getData()when (result) {is Success -> //do somethingis Error -> // show error}}}}

Выглядит неплохо. Мы можем возвращать данные и обрабатывать ошибку.
Теперь посмотрим как выглядит репозиторий в текущем варианте

class RepositoryImpl(private val dataSource: DataSource) : Repository {  override suspend fun getData(): Reaction<Int> {return try {Reaction.Success(dataSource.data)} catch(e: Exception) {Reaction.Error(e)}}}

Из-за того, что каждый метод репозитория должен возвращать Reaction, придется каждый метод оборачивать в try-catch, что выглядит некрасиво из-за огромного количества бойлерплейт кода. Попробуем сделать код чище, выносом try-catch в метод.

sealed class Reaction<out T> {   class Success<out T>(val data: T) : Reaction<T>()   class Error(val exception: Throwable) : Reaction<Nothing>()   companion object {       inline fun <T> on(f: () -> T): Reaction<T> = try {           Success(f())       } catch (ex: Exception) {           Error(ex)       }   }}

После этого репозиторий начнет выглядеть так:

class RepositoryImpl(private val dataSource: DataSource) : Repository {suspend fun getData(): Reaction<Int> = Reaction.on { dataSource.data }}

Видно, что код стал гораздо чище и только в этом примере мы сэкономили 4 строки кода.

Теперь вернемся к ViewModel и постараемся убрать бойлерплэйт when для каждого запроса. Сейчас мы получаем данные, обрабатываем и отдаем во View.

class MyViewModel : ViewModel {private val repository: Repositoryprivate val _onData = MutableLiveData<State>()val onData: LiveData<State> = _onDatafun doSomething() {viewModelScope.launch(Dispatchers.IO) {val result = repository.getData()when (result) {is Success -> _onData.postValue(State.Success)is Error -> onData.postValue(State.Error(result.message))}}}sealed class State {  object Progress : State()  object Success : State()  data class Error(message: String) : State()}}

Решение уже подсказывает опыт RxJava, Coroutines и LiveData.
Исходя из того, что данные, которые вернулись в ViewModel обычно надо показать пользователю в виде результата запроса, либо ошибки, давайте добавим метод zip, который будет приводить Reaction к объекту, который будет передаваться в LiveData

inline fun <T, R> Result<T>.zip(success: (T) -> R, error: (Exception) -> R): R =   when (this) {       is Reaction.Success -> success(this.data)       is Reaction.Error -> error(this.exception)   }

Наша MyViewModel преобразится в

class MyViewModel : ViewModel {private val repository: Repositoryprivate val _onData = MutableLiveData<State>()val onData: LiveData<State> = _onNewDirectoryfun doSomething() {viewModelScope.launch(Dispatchers.IO) {repository.getData().zip(        { State.Success },         { State.Error(result.message) }        ).let { onData.postValue(it) }}}//...}

Также есть частый случай, когда метод в ViewModel делает несколько последовательных запросов к репозиторию, где одни данные зависят от ранее полученных. При получении ошибки необходимо прервать цепочку запросов и вернуть ошибку во View

Рассмотрим следующий пример:

class MyViewModel : ViewModel {//...fun doSomething() {viewModelScope.launch(Dispatchers.IO) {var firstData: Int = 0val reaction = repository.getData()when (reaction) {is Success -> firstData = reaction.data is Error -> {onData.postValue(State.Error(reaction.message))return@launch}}val nextReaction = repository.getNextData(firstData)      //..}}  //...}

Решений можно придумать множество, но я здесь представлю решение без callback hell, оставляя преимущество, которое предоставляет использование Coroutines

class MyViewModel : ViewModel {  //...fun doSomething() {viewModelScope.launch(Dispatchers.IO) {val firstData = repository.getData().takeOrReturn {onData.postValue(State.Error(result.message)return@launch}val nextReaction= repository.getNextData(firstData)      //..}}}

В итоге мы имеем легко расширяемое решение, в которой уже есть такие популярные методы обработки полученных данных как:

  • on - Создает Reaction из выражения

  • map - Трансформирует успешный результат

  • flatMap - Трансформирует успешный результат в новую Reaction

  • doOnSuccess - Выполняется, если Reaction - успешный результат

  • и др

Полный список и дополнительные примеры можно найти в Github

Сравнение с аналогами

Было найдено 3 аналога. Ниже представлены сами аналоги и их преимущества и недостатки

  • Railway Kotlin
    Преимущества:

    • Легко освоить

    • Состоит из 1 файла

    Недостатки:

    • Нет возможности инкапсулировать try-catch

    • Использование infix методов

    • Неинтуитивные названия методов

  • Arrow-KT
    Преимущества:

    • Популярная библиотека

    Недостатки:

    • Из описания непонятно что библиотека может

    • Высокий порог вхождения по сравнению с аналогами

    • Оставляет ощущение, что является слишком сложной для решения такой простой проблемы

  • Result (Kotlin)
    Преимущества:

    • Является почти полной копией предлагаемого мной решения

    Недостатки:

Итог

Reaction - это легковесная библиотека с минимальным порогом вхождения, т.к. она состоит из 1 файла, предоставляющая такие же мощности, как решение от Kotlin, но не содержит всех его минусов.

GitHub

https://github.com/taptappub/Reaction/

Источник: habr.com
К списку статей
Опубликовано: 07.03.2021 20:08:29
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Open source

Разработка под android

Kotlin

Kotlin result

Reaction

Android

Android development

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru