Anyone who has used Clean Architecture in development has faced the problem of transferring data between layers. The essence of the problem is always the same: you need to return either a result or an error. This can be represented, for example, like this:
interface Reaction
data class Success(val data: String) : Reaction
data class Error(message: String) : Reaction
Depending on the task, such reactions can be very different, so let's combine it into one class using Generics and Sealed classes .
sealed class Reaction<out T> {
class Success<out T>(val data: T) : Reaction<T>()
class Error(val exception: Throwable) : Reaction<Nothing>()
}
Let's look at an example of how it can be used.
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 -
3 .
-
1
:
try-catch
infix
Arrow-KT
:
:
,
-
:
Reaction is a lightweight library with a minimal threshold of entry. it consists of 1 file, which provides the same power as the solution from Kotlin, but does not contain all its cons.
Github
https://github.com/taptappub/Reaction/