Hey all - how would you recommend handling corouti...
# coroutines
z
Hey all - how would you recommend handling coroutines in a multi-module app? I know within a single module unit tests run sequentially, but if you have multi modules they appear to be able to run in parallel (i.e. sequentially within each module but parallel within the overall test process) If I use
Dispatchers.resetMain
after tests in module 1 it can affect tests in module 2, 3, ... x since it's using a singleton to control the main dispatcher and the tests for each module can run at the same time….
k
This is one of the reasons to not use
Dispatchers.setMain
and
Dispatchers.resetMain
. You should instead consider injecting your dispatchers to your code under test so that you can control dispatchers locally within a test, not globally.
👍 2
z
I could certainly do that. I worry not all sdks and libs would work this way, but yeah - totally understood. Stupid singletons!
k
They don’t all work that way, and you should likely call out when they don’t. AndroidX
viewModelScope
is now discouraged by google because of its static dependency on
Dispatchers.Main
.
👍 1
😂 1
z
Are there any good examples of a custom coroutine scope in place of
viewModelScope
? That seems to be the biggest question mark for this. I've got dispatchers being injected everywhere else as far as I can tell in my code/libraries, so I can control those no problem
🧵 1
i
The release notes for Lifecycle have an example: https://developer.android.com/jetpack/androidx/releases/lifecycle#2.5.0
👍 1
z
Thanks folks! Will take a look at all this!
Quick follow up: for testing this setup, without a coroutine test rule is it safe to use the test coroutines still? I assume yes but wasn't sure...
🧵 1
k
I don’t use any coroutine test rules so yes, it should be safe. But I am unsure of your specific use case.
👍 1
z
Great! I did run into one issue (so far). Seems paging3
cachedIn
API causes some issues and requires the coroutine test rule to function. I can't see why though. I created a paging issue to track this: we'll see... https://issuetracker.google.com/issues/324108012 Maybe I am doing something wrong - (hopefully!)
🧵 2
c
Please don't use "also send to channel", you're spamming everyone.
👍 1
z
apologies.
a
Hi Folks, If I follow this doc to avoid directly using
viewModelScope
. https://developer.android.com/jetpack/androidx/releases/lifecycle#2.5.0 Does this create additional
Job()
?
Copy code
class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}
k
It depends what you pass in to the constructor of your
CloseableCoroutineScope
at call site. If you pass in nothing, the default will be to create a new supervisor job.
a
What is recommended to be passed here? My current code.
CloseableCoroutineScope
Copy code
public class CloseableCoroutineScope(
    private val dispatcherProvider: DispatcherProvider,
    context: CoroutineContext = SupervisorJob() + dispatcherProvider.mainImmediate,
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
    }
}
CloseableCoroutineScopeModule
Copy code
@Module
@InstallIn(SingletonComponent::class)
public object CloseableCoroutineScopeModule {
    @Provides
    public fun providesCloseableCoroutineScope(
        dispatcherProvider: DispatcherProvider,
    ): CloseableCoroutineScope {
        return CloseableCoroutineScope(
            dispatcherProvider = dispatcherProvider,
        )
    }
}
ViewModel
Copy code
@HiltViewModel
internal class SettingsScreenViewModel @Inject constructor(
    private val closeableCoroutineScope: CloseableCoroutineScope,
) : ViewModel(closeableCoroutineScope) {
i
Note that as of Lifecycle 2.8.0-alpha03 and higher, you don't need to do any of this at all since
viewModelScope
is a constructor parameter directly: https://developer.android.com/jetpack/androidx/releases/lifecycle#2.8.0-alpha03
👍 1
thank you color 1
a
Hi @Ian Lake,
Copy code
class MyViewModel(
  // Make Dispatchers.Main the default, rather than Dispatchers.Main.immediate
  viewModelScope: CoroutineScope = Dispatchers.Main + SupervisorJob()
) : ViewModel(viewModelScope) {
  // Use viewModelScope as before, without any code changes
}
Is this
SupervisorJob()
recommended to be used or we can avoid that? Can you please share a bit more on that? Thank You.
i
It wouldn't be there if it wasn't what you should do
👌 1
thank you color 1