[Open source, Разработка под Android, Kotlin] Reaction — обработка результатов методов в Kotlin
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Каждый, кто использовал Чистую архитектуру в разработке, сталкивался с проблемой передачи данных между слоями. Суть проблемы всегда одинакова: необходимо вернуть либо результат, либо ошибку. Представить это можно, например, так:
interface Reaction
data class Success(val data: String) : Reaction
data 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: Repository
fun doSomething() {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.getData()
when (result) {
is Success -> //do something
is 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: Repository
private val _onData = MutableLiveData<State>()
val onData: LiveData<State> = _onData
fun 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: Repository
private val _onData = MutableLiveData<State>()
val onData: LiveData<State> = _onNewDirectory
fun 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 = 0
val 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)
Преимущества:
- Является почти полной копией предлагаемого мной решения
Недостатки:
- Нельзя использовать как возвращаемое значение
- Можно включить использование в gradle, но на свой страх и риск, потому что Result, является частью языка Kotlin и может поменять поведение
ИтогReaction - это легковесная библиотека с минимальным порогом вхождения, т.к. она состоит из 1 файла, предоставляющая такие же мощности, как решение от Kotlin, но не содержит всех его минусов.GitHubhttps://github.com/taptappub/Reaction/
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка под iOS, Разработка под Android, Управление продажами, Презентации] Онбординг. Зачем нужен и как использовать
- [Open source, Разработка игр, Графический дизайн, Дизайн игр, DIY или Сделай сам] О ходе создания русской народной игры «Колобок» в феврале
- [Open source, Java, OpenStreetMap, Геоинформационные сервисы] Как использовать GraphHopper для построения пешеходных маршрутов по собственным правилам
- [Open source, Программирование, Системное программирование, Компиляторы, Rust] Планирование редакции Rust 2021 (перевод)
- [Java, Разработка мобильных приложений, Разработка под Android, Kotlin] Android — ViewPager2 — заменяем фрагменты на лету (программно)
- [Open source, Алгоритмы, Lua, Параллельное программирование] Параллелизм и плотность кода
- [Высокая производительность, Разработка мобильных приложений, IT-инфраструктура, Разработка под Android] Как мы ускорили запуск приложения Dropbox для Android на 30 % (перевод)
- [Perl, Информационная безопасность, Open source, DNS] Угон домена Perl.com (перевод)
- [Open source, Реверс-инжиниринг, Видеокарты, Процессоры] Реверс-инжиниринг GPU Apple M1 (перевод)
- [Программирование, Разработка мобильных приложений, Разработка под Android, Kotlin] Влияние data-классов на вес приложения
Теги для поиска: #_open_source, #_razrabotka_pod_android (Разработка под Android), #_kotlin, #_kotlin_result, #_reaction, #_android, #_android_development, #_kotlin, #_open_source, #_razrabotka_pod_android (
Разработка под Android
), #_kotlin
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:14
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Каждый, кто использовал Чистую архитектуру в разработке, сталкивался с проблемой передачи данных между слоями. Суть проблемы всегда одинакова: необходимо вернуть либо результат, либо ошибку. Представить это можно, например, так: interface Reaction
data class Success(val data: String) : Reaction data class Error(message: String) : Reaction 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: Repository fun doSomething() { viewModelScope.launch(Dispatchers.IO) { val result = repository.getData() when (result) { is Success -> //do something is 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) } } } 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 } } class MyViewModel : ViewModel {
private val repository: Repository private val _onData = MutableLiveData<State>() val onData: LiveData<State> = _onData fun 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() } } Исходя из того, что данные, которые вернулись в 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) } class MyViewModel : ViewModel {
private val repository: Repository private val _onData = MutableLiveData<State>() val onData: LiveData<State> = _onNewDirectory fun doSomething() { viewModelScope.launch(Dispatchers.IO) { repository.getData() .zip( { State.Success }, { State.Error(result.message) } ) .let { onData.postValue(it) } } } //... } class MyViewModel : ViewModel {
//... fun doSomething() { viewModelScope.launch(Dispatchers.IO) { var firstData: Int = 0 val 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) //.. } } //... } 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) //.. } } }
=========== Источник: habr.com =========== Похожие новости:
Разработка под Android ), #_kotlin |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:14
Часовой пояс: UTC + 5