-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathStandardViewModel.kt
97 lines (82 loc) · 3 KB
/
StandardViewModel.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.khoshnaw.viewmodel.standard
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.khoshnaw.entity.ErrorMessage
import com.khoshnaw.usecase.movie.base.InputPort
import com.khoshnaw.usecase.movie.base.OutputPort
import com.khoshnaw.viewmodel.mvi.MVIIntent
import com.khoshnaw.viewmodel.mvi.MVIState
import com.khoshnaw.viewmodel.mvi.MVIViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
@Suppress("MemberVisibilityCanBePrivate")
abstract class StandardViewModel<S : MVIState, I : MVIIntent>(
private val backgroundDispatcher: CoroutineContext = Dispatchers.IO
) : MVIViewModel<S, I>() {
override val intents: Channel<I> by lazy {
Channel<I>().tryToConsume()
}
private val _state = MutableLiveData<S>()
override val state: LiveData<S> = _state
val error = Channel<ErrorMessage>()
init {
tryToInjectOutputPorts()
}
//region intent
private fun Channel<I>.tryToConsume(): Channel<I> {
launchInIO { tryTo { consumeAsFlow().collect { tryToHandleIntent(it) } } }
return this@tryToConsume
}
private suspend fun tryToHandleIntent(intent: I) = tryTo {
handleIntent(intent)
}
protected open suspend fun handleIntent(intent: I): Any = Unit
//endregion intent
//region injection
private fun <O : OutputPort> O.tryToInjectOutputPorts() {
launchInIO { tryTo { injectOutputPorts() } }
}
private suspend fun <O : OutputPort> O.injectOutputPorts() = this::class.memberProperties.map {
it.isAccessible = true
it.getter.call(this)
}.filterIsInstance<InputPort<O>>().forEach {
it.registerOutputPort(this@injectOutputPorts)
}
//endregion injection
//region error
open fun updateError(e: Throwable) = updateError(ErrorMessage.DEFAULT)
open fun updateError(message: ErrorMessage) {
launchInIO { error.send(message) }
}
//endregion error
//region utils
protected suspend fun tryTo(callback: suspend () -> Unit) = try {
callback()
} catch (e: Throwable) {
Timber.e(e)
updateError(e)
}
protected fun <T> Flow<T>.collectResult(
action: (value: T) -> Unit
) = launchInIO { tryTo { collect { tryTo { action(it) } } } }
protected fun updateState(state: S) = _state.postValue(state)
protected fun launchInIO(
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
) = viewModelScope.launch(
context = backgroundDispatcher,
start = start,
block = block,
)
//endregion utils
}