Kotlin Coroutines - oppia/oppia-android GitHub Wiki
Kotlin coroutines
The team leverages Kotlin coroutines for background processing, however we never launch a new dispatcher or use any of the built-in dispatchers. Instead, we use one of our existing dispatchers:
- BackgroundDispatcher: for executing expensive operations
- BlockingDispatcher: for executing operations that need to be done in parallel (generally, don't use this--prefer InMemoryBlockingCache, instead)
New operations should create a separate scope using one of the existing dispatchers & perform their execution using that. Note that failures in operations will cause the scope itself to enter a failed state, so scopes shouldn't be kept long-term for correctness.
Synchronizing state in tests
One major benefit in consolidating all execution on the same coroutine dispatchers is that facilitates easy thread synchronization boundaries in tests. TestCoroutineDispatchers is a test-injectable utility with a number of useful API functions:
(un)registerIdlingResource
: ensures background operations finish automatically before performing Espresso UI interactionsrunCurrent
: run all background tasks that can be completed now without advancing the clock (Robolectric tests run with a fake clock that has to be manually advanced)advanceTimeBy
/advanceUntilIdle
: functions for running tasks scheduled in the future
Generally, registering an idling resource for shared Espresso/Robolectric tests and calling runCurrent
after performing any operations in the bodies of tests is sufficient to guarantee no test flakes for nearly all scenarios. There are many examples of using both throughout the codebase.
advanceTimeBy
/advanceUntilIdle
should only be used in cases where they are specifically needed (prefer runCurrent
where possible since advanceUntilIdle
is more of a "sledgehammer" solution and should rarely be necessary).