Dragos Rachieru
03/21/2022, 10:27 AMviewModelScope
inside it and use that, so maybe using rememberCoroutineScope()
with it, but I don't know how to do it properly.Dragos Rachieru
03/21/2022, 10:27 AM@Composable
fun <VM : ViewModel> createViewModel(
scope: CoroutineScope,
typeToken: TypeToken<VM>
): VM {
val viewModel: VM by localDI().Instance(typeToken)
viewModel.initialise(scope)
return viewModel
}
@Composable
inline fun <reified VM : ViewModel> createViewModel(
scope: CoroutineScope = rememberCoroutineScope()
): VM {
return createViewModel(scope, generic())
}
Dragos Rachieru
03/21/2022, 10:29 AMComposable calls are not allowed inside the calculation parameter of inline fun <T> remember(calculation: () -> TypeVariable(T)): TypeVariable(T)
Dragos Rachieru
03/21/2022, 10:29 AMViewModel
I came up with:
abstract class ViewModel {
lateinit var scope: CoroutineScope
open fun onCreate() {
//todo
}
internal fun initialise(scope: CoroutineScope) {
this.scope = scope
onCreate()
}
}
hfhbd
03/21/2022, 12:00 PMrememberCoroutineScope()
should be only used for your UI, eg onClick callbacks, snackbar etc. Don't use this scope for logic in your ViewModel
, otherwise a recomposition could cancel suspended jobs in your ViewModel! For ViewModels, you could create its own CoroutineScope
, eg: CoroutineScope(Dispatchers.Default)
Dragos Rachieru
03/21/2022, 12:27 PMhfhbd
03/21/2022, 12:45 PMDragos Rachieru
03/21/2022, 12:50 PMonDestroy
is called)Dragos Rachieru
03/21/2022, 12:51 PMViewModel objects are scoped to the Lifecycle passed to the ViewModelProvider when getting the ViewModel. The ViewModel remains in memory until the Lifecycle it's scoped to goes away permanently: in the case of an activity, when it finishes, while in the case of a fragment, when it's detached.
I think I need to cancel
the scope when the screen is no longer shownhfhbd
03/21/2022, 12:54 PMDragos Rachieru
03/21/2022, 12:56 PMhfhbd
03/21/2022, 12:57 PMDragos Rachieru
03/21/2022, 12:58 PMrememberCoroutineScope
enough for that? I thought it remembers the scope
through recompositions and cancels only when it is no longer composedhfhbd
03/21/2022, 1:27 PM@ExperimentalTime
@Composable
fun Content() {
var show by remember { mutableStateOf(true) }
if (show) {
First { show = false }
} else {
Second()
}
}
@ExperimentalTime
@Composable
fun First(onClick: () -> Unit) {
val scope = rememberCoroutineScope()
Button({
scope.launch {
something()
}
onClick()
}) {
Text("Hello")
}
}
@ExperimentalTime
suspend fun something() {
println("Start")
delay(5.seconds)
println("End")
}
@Composable
fun Second() {
Text("Second")
}
End
will never be printed.Dragos Rachieru
03/21/2022, 1:41 PMDragos Rachieru
03/21/2022, 1:44 PMstateIn
so I can collect a flow for any changes while the view is shown(I also do some mapping and that's why collectAsState
is not enough)Casey Brooks
03/21/2022, 2:44 PMrememberCoroutineScope()
to Kodein https://kodein.org/Kodein-DI/?6.3/core#_factory_bindingDragos Rachieru
03/22/2022, 9:43 AMDragos Rachieru
03/22/2022, 9:45 AMViewModel
Casey Brooks
03/22/2022, 2:46 PMviewModelScope.launch { }
blocks
interface SomeRepository {
suspend fun getSavedCount(): Int
suspend fun saveCount(count: Int)
}
class MyViewModel(
val viewModelScope: CoroutineScope, // passed from Compose through Kodein's Factory
val repository: SomeRepository, // passed normally from Kodein
) {
data class State(
val count: Int = 0
)
private val _state = MutableStateFlow(State())
val state: StateFlow<State> get() = _state.asStateFlow()
fun initialize() {
viewModelScope.launch {
val savedCount = repository.getSavedCount()
_state.update { it.copy(count = savedCount) }
}
}
fun increment() {
viewModelScope.launch {
val newValue = _state.updateAndGet { it.copy(count = it.count + 1) }
repository.saveCount(newValue.count)
}
}
fun decrement() {
viewModelScope.launch {
val newValue = _state.updateAndGet { it.copy(count = it.count - 1) }
repository.saveCount(newValue.count)
}
}
}
val kodein = Kodein {
bind<MyViewModel>() with factory { viewModelScope: CoroutineScope -> MyViewModel(viewModelScope, instance()) }
}
Dragos Rachieru
03/25/2022, 12:55 PMviewModelScope
come from?Casey Brooks
03/25/2022, 1:53 PMfactory
, you can pass some params in when you retrieve it (like Dagger’s @AssistedInject
). So the viewModelScope
gets passed in from the composition, while its other dependencies come from the Kodein container
kotlin
@Composable
fun MyApp() {
val viewModelScope = rememberCoroutineScope()
val viewModel: MyViewModel = remember(viewModelScope) { kodein.direct.instance(arg = viewModelScope) }
val vmState by viewModel.state.collectAsState()
MyAppUi(
state = vmState,
onIncrement = { viewModel.increment() },
onDecrement = { viewModel.decrement() },
)
}