Skip to content

Commit

Permalink
Fix Dispatchers.Main not being fully initialized on Android and Swing (
Browse files Browse the repository at this point in the history
…#3101)

* Fix Dispatchers.Main not being fully initialized on Android

If `unitTests.returnDefaultValues=true` is set, then
`Looper.getMainLooper()` may return `null`. The type system of
Kotlin is tricked to believe that the method can't ever return
`null`, so doesn't check for it anywhere. As a result, despite not
being fully initialized, `Dispatchers.Main` is considered
correct.

This was not an issue before, as it only surfaced when
`Dispatchers.Main` was used. However, now, `Main` is the source of
time for delays, so any delay will throw something
incomprehensible if this happens.
  • Loading branch information
dkhalanskyjb committed Dec 21, 2021
1 parent 69bc2a3 commit bbb175b
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 2 deletions.
Expand Up @@ -14,6 +14,11 @@ public interface MainDispatcherFactory {
/**
* Creates the main dispatcher. [allFactories] parameter contains all factories found by service loader.
* This method is not guaranteed to be idempotent.
*
* It is required that this method fails with an exception instead of returning an instance that doesn't work
* correctly as a [Delay].
* The reason for this is that, on the JVM, [DefaultDelay] will use [Dispatchers.Main] for most delays by default
* if this method returns an instance without throwing.
*/
public fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher

Expand Down
6 changes: 4 additions & 2 deletions ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt
Expand Up @@ -51,8 +51,10 @@ public sealed class HandlerDispatcher : MainCoroutineDispatcher(), Delay {

internal class AndroidDispatcherFactory : MainDispatcherFactory {

override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
HandlerContext(Looper.getMainLooper().asHandler(async = true))
override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher {
val mainLooper = Looper.getMainLooper() ?: throw IllegalStateException("The main looper is not available")
return HandlerContext(mainLooper.asHandler(async = true))
}

override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"

Expand Down
10 changes: 10 additions & 0 deletions ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt
Expand Up @@ -74,6 +74,16 @@ private object ImmediateSwingDispatcher : SwingDispatcher() {
* Dispatches execution onto Swing event dispatching thread and provides native [delay] support.
*/
internal object Swing : SwingDispatcher() {

/* A workaround so that the dispatcher's initialization crashes with an exception if running in a headless
environment. This is needed so that this broken dispatcher is not used as the source of delays. */
init {
Timer(1) { }.apply {
isRepeats = false
start()
}
}

override val immediate: MainCoroutineDispatcher
get() = ImmediateSwingDispatcher

Expand Down

0 comments on commit bbb175b

Please sign in to comment.