Reputation: 43
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
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
Reputation: 123
Add this line in your proguard file
-keep class kotlinx.coroutines.android.* {*;}
Upvotes: 1
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:
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.launch
inside that click listener.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