creative_nick
creative_nick

Reputation: 43

Why application terminates when coroutine tries to enter Dispatchers.Main?

I am trying to add coroutine that firstly sets progressBar visible, then asks server for data and, when it gets the data, progressBar is set invisible. I have read that to interact with UI my coroutine needs to operate in Dispatcher.Main, but when I try to set launch(Dispatcher.Main) whole application terminates without an error.

I started following tutorial from: https://www.kotlindevelopment.com/deep-dive-coroutines/. I changed a part from code shown there:

launch(UI) {
  progressBar.visibility = View.VISIBLE
  try {
    val userString = fetchUserString("1").await()
    val user = deserializeUser(userString).await()
    showUserData(user)
  } catch (ex: Exception) {
    log(ex)
  } finally {
    progressBar.visibility = View.GONE
  }
}

to:

GlobalScope.launch(Dispatchers.Main) {
                progressBarMarkers.visibility = View.VISIBLE
                try {
                    val repository = MarkerRepository()
                    points = repository.getAllDataAsync().await()
                    }
               } catch (ex: Exception) {
                    Log.d("EXCEPTION", ex.toString())
               } finally {
                    progressBarMarkers.visibility = View.INVISIBLE
                }
            }

but it didn't work. I started searching for what could be the problem and I found out that when my coroutine looks as shown below the app terminates when it reaches withContext(Dispatchers.Main)

GlobalScope.launch{
        val button = findViewById<ProgressBar>(R.id.progressBarMarkers)
        withContext(Dispatchers.Main){
                button.visibility = View.VISIBLE
        }
}

I am still very new to Kotlin and coroutines, so maybe it is just some basic mistake, but I couldn't find the answer why the application terminates, and whats more terminates without an error

Whole coroutine is in:

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private lateinit var mMap: GoogleMap
    private val REQUEST_PERMISSION_CODE: Int = 123
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)
        val mapFragment = supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)

        ShowPlacesButton.setOnClickListener {
            launch{
                val button = findViewById<ProgressBar>(R.id.progressBarMarkers)
                withContext(Dispatchers.Main + Job()){
                    button.visibility = View.VISIBLE
                }
        }
    }
}

and parts of my gradle file:

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.google.android.gms:play-services-maps:16.1.0'
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
    implementation 'com.google.dagger:dagger:2.13'
    kapt 'com.google.dagger:dagger-compiler:2.13'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
}

Why does the app terminate, when it reaches Dispatcher.Main?

Upvotes: 4

Views: 5664

Answers (3)

Narendra_Nath
Narendra_Nath

Reputation: 5173

This is the same error I got when I was using Retrofit to fetch some data from the internet inside the coroutine Scope.

The fix was pretty simple which was turning the internet on in my testing physical device.

Upvotes: 0

ThavaSelvan
ThavaSelvan

Reputation: 123

Add this line in your proguard file

-keep class kotlinx.coroutines.android.* {*;}

Upvotes: 1

RBusarow
RBusarow

Reputation: 754

Do you maybe have "show only selected application" enabled in logcat? Or some other sort of filter?

Because when I run your code, I get this very helpful crash:

E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
    Process: com.idunnno.test, PID: 27501
    java.lang.IllegalStateException: Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android'
        at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.missing(MainDispatchers.kt:73)
        at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.isDispatchNeeded(MainDispatchers.kt:54)
        at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:373)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25)
        at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:152)
        at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
        at com.idunnno.daggertest.MainActivity$onCreate$1$1.invokeSuspend(MainActivity.kt:27)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)

The basic fix is to add this line to your Gradle dependencies:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"

Some other notes:

  1. You don't want to use findViewById inside a click listener. You should be performing that task once - probably in onCreate - and then saving it off to a class-level property.
  2. You don't really need to be using a launch inside that click listener.
  3. If you're going to use coroutines, then the class should probably just implement CoroutineScope. This means you don't have to create a new context or use GlobalScope every time you want to use launch or async.

Upvotes: 4

Related Questions