zak.taccardi
04/13/2020, 6:18 PMCoroutineDispatcher for LifecycleCoroutineScope in production?
Seems it’s hardcoded to Dispatchers.Main.immediate and SupervisorJob which is something I desire flexibility around
https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescopewasyl
04/13/2020, 6:24 PMzak.taccardi
04/13/2020, 6:24 PMzak.taccardi
04/13/2020, 6:25 PMDispatchers.Main.immediate is a violation of thatwasyl
04/13/2020, 6:26 PMwe provide the ability to inject the dispatchersYes but why 😄
wasyl
04/13/2020, 6:27 PMzak.taccardi
04/13/2020, 6:27 PMCoroutineName for debugging purposes, who knowsfatih
04/13/2020, 6:34 PMzak.taccardi
04/13/2020, 6:35 PMif you are using daggerno
fatih
04/13/2020, 6:35 PMzak.taccardi
04/13/2020, 6:36 PMcreating custom annotation for each dispatcherNot using reflection, java 8, or annotation processing
wasyl
04/13/2020, 6:37 PMzak.taccardi
04/13/2020, 6:38 PMzak.taccardi
04/13/2020, 6:39 PMwasyl
04/13/2020, 6:42 PMzak.taccardi
04/13/2020, 6:49 PMFragment, you can inject your own ViewModelzak.taccardi
04/13/2020, 6:50 PM.viewModelScope is an anti-pattern because it prevents injecting a CoroutineScope, so I don’t use itzak.taccardi
04/13/2020, 6:50 PMviewModelScope to work when under test is a jokezak.taccardi
04/13/2020, 6:52 PMLifecycleScope are very useful, like launchWhenStartedzak.taccardi
04/13/2020, 7:07 PMrkeazor
04/14/2020, 6:26 AMzak.taccardi
04/14/2020, 2:33 PMViewModel to construct your own non-jetpack ViewModel (without onCleared()) with a CoroutineScope that is cancelled in the jetpack’s onCleared().
Injecting a CoroutineScope means you have no need to leverage onCleared() because CoroutineScope provides its own method for completion callback via CompletionHandler.
There’s no better, cleaner way to do it.rkeazor
04/14/2020, 7:06 PMzak.taccardi
04/14/2020, 7:07 PMrkeazor
04/14/2020, 7:08 PMzak.taccardi
04/14/2020, 7:08 PMCoroutineScope from runBlocking { .. }rkeazor
04/14/2020, 7:09 PMrkeazor
04/14/2020, 7:10 PMfun CoroutineScope.addNumbers(...)zak.taccardi
04/14/2020, 7:10 PMrkeazor
04/14/2020, 7:10 PMzak.taccardi
04/14/2020, 7:10 PMrkeazor
04/14/2020, 7:10 PMsuspend fun addNumnbers() { coroutineScope {rkeazor
04/14/2020, 7:11 PMrkeazor
04/14/2020, 7:11 PMzak.taccardi
04/14/2020, 7:11 PM.viewModelScope ignores the runBlocking you are wrapping your tests with because you violate structured concurrencyrkeazor
04/14/2020, 7:11 PMzak.taccardi
04/14/2020, 7:12 PMJob instance is when you are doing something very complexzak.taccardi
04/14/2020, 7:14 PMrkeazor
04/14/2020, 7:14 PMrkeazor
04/14/2020, 7:14 PMrkeazor
04/14/2020, 7:15 PMfun addSomething(x: Int, y: Int) {
launch { x + y }
.....
}rkeazor
04/14/2020, 7:15 PMrkeazor
04/14/2020, 7:16 PMzak.taccardi
04/14/2020, 7:16 PMrkeazor
04/14/2020, 7:16 PMzak.taccardi
04/14/2020, 7:16 PMzak.taccardi
04/14/2020, 7:17 PMzak.taccardi
04/14/2020, 7:17 PMDispatchers.Default in production and the blocking dispatcher from runBlocking { .. } when testing (or runBlockingTest(..))zak.taccardi
04/14/2020, 7:18 PMCoroutineScope injectionzak.taccardi
04/14/2020, 7:18 PMCoroutineScope into a class over extending Jetpack’s ViewModel achievesrkeazor
04/14/2020, 7:20 PMrkeazor
04/14/2020, 7:21 PMrkeazor
04/14/2020, 7:22 PMrkeazor
04/14/2020, 7:23 PMrkeazor
04/14/2020, 7:23 PMzak.taccardi
04/14/2020, 7:24 PMCoroutineScope. That CoroutineScope is then provided via the actual Jetpack ViewModel, which is abstracted via a single function call. This function is a standalone module that uses the Jetpack ViewModel as an implementation dependency so the rest of the codebase is not coupled to the Jetpack `ViewModelzak.taccardi
04/14/2020, 7:25 PMa rule is like one lineOne line per test. And if you forget to use it, test fails. Also breaks the ability to execute tests in parallel because you now rely on shared static state
rkeazor
04/14/2020, 7:26 PMrkeazor
04/14/2020, 7:28 PMzak.taccardi
04/14/2020, 7:28 PMMyViewModel is scoped to. I usually scope to user flows rather than UI components like Fragments because then I can’t share that MyViewModel with other fragments. But this is abstracted by how the MyViewModel is providedrkeazor
04/14/2020, 7:29 PMzak.taccardi
04/14/2020, 7:29 PMzak.taccardi
04/14/2020, 7:29 PMrkeazor
04/14/2020, 7:30 PMrkeazor
04/14/2020, 7:30 PMrkeazor
04/14/2020, 7:31 PMzak.taccardi
04/14/2020, 7:32 PMClass<T>, it’s Class<T> + CoroutineScoperkeazor
04/14/2020, 7:33 PMzak.taccardi
04/14/2020, 7:34 PMrkeazor
04/14/2020, 7:34 PMrkeazor
04/14/2020, 7:35 PMzak.taccardi
04/14/2020, 7:35 PMzak.taccardi
04/14/2020, 7:35 PMDispatchers.setMain(..) anywhere because we inject our dispatcherszak.taccardi
04/14/2020, 7:35 PMrkeazor
04/14/2020, 7:36 PMrkeazor
04/14/2020, 7:37 PMzak.taccardi
04/14/2020, 7:38 PMDispatchers.Main for performance reasons for our ViewModel s. So this would mean that every viewModelScope.launch { } would be incorrect because it would need to be viewModelScope.launch(Dispatchers.Default) { .. } every time.
and to guarantee consistency there, that would mean to write a lint rule that would be unnecessaryrkeazor
04/14/2020, 7:38 PMzak.taccardi
04/14/2020, 7:39 PMdispatchers.main vs Dispatchers.Main is straight forwardzak.taccardi
04/14/2020, 7:39 PMrkeazor
04/14/2020, 7:43 PMzak.taccardi
04/14/2020, 7:44 PMState, exposed as Flow<State>, does not require the UI thread to build it fullyrkeazor
04/14/2020, 7:46 PMrkeazor
04/14/2020, 7:46 PMrkeazor
04/14/2020, 7:47 PMzak.taccardi
04/14/2020, 7:48 PMzak.taccardi
04/14/2020, 7:49 PMAndroidSchedulers.MAIN outside of the place you construct your dispatchersrkeazor
04/14/2020, 7:51 PMzak.taccardi
04/14/2020, 7:51 PMzak.taccardi
04/14/2020, 7:51 PMinternal class ViewModelHolder<T>(
private val coroutineScope: CoroutineScope,
val viewModel: T
) : AndroidXViewModel() {
override fun onCleared() {
coroutineScope.cancel()
}
}rkeazor
04/14/2020, 7:51 PMzak.taccardi
04/14/2020, 7:52 PMzak.taccardi
04/14/2020, 7:52 PMrkeazor
04/14/2020, 7:52 PMrkeazor
04/14/2020, 7:53 PMrkeazor
04/14/2020, 7:53 PMzak.taccardi
04/14/2020, 7:54 PMzak.taccardi
04/14/2020, 7:55 PMzak.taccardi
04/14/2020, 7:57 PMrkeazor
04/14/2020, 7:59 PMzak.taccardi
04/14/2020, 8:00 PMrkeazor
04/14/2020, 8:00 PMrkeazor
04/14/2020, 8:01 PMrkeazor
04/14/2020, 8:02 PMrkeazor
04/14/2020, 8:02 PMrkeazor
04/14/2020, 8:02 PMrkeazor
04/14/2020, 8:03 PMzak.taccardi
04/14/2020, 8:03 PMViewModel is nothing but a scoping mechanism. You can take that scoping mechanism and add additional responsibility to it, like building states for a view to consume. Of course, while minor, this violates single responsibilityzak.taccardi
04/14/2020, 8:03 PMzak.taccardi
04/14/2020, 8:04 PMBoth ViewModels are already testable and ViewModelScopeEasy to test and difficult to test are two different things
zak.taccardi
04/14/2020, 8:05 PMi cant see the benefitYou want to have some `ViewModel`s do background work in
Dispatchers.Default. While others have a requirement to use Dispatchers.Main. How do you achieve that with .viewModelScope in a non-error prone way?rkeazor
04/14/2020, 8:09 PMrkeazor
04/15/2020, 12:27 AMrkeazor
04/15/2020, 12:33 AMrkeazor
04/15/2020, 12:42 AMzak.taccardi
04/15/2020, 4:59 PMViewmodelscope, is just a coroutineScope that is already created for you and cleared for you in Viewmodel.onClear.... There is no more complexity pass that.this is becomes very complex if you need to: 1. change which dispatcher is used - in prod:
Dispatchers.Main vs Dispatchers.Default
- in test: TestCoroutineDispatcher
2. Cancellation semantics:
- in prod: ViewModel.onCleared(..)
- in test: when @Test fun testFunction() completeszak.taccardi
04/15/2020, 5:01 PM