I have an initialization block in onCreateView
, where some variables are assigned from SharedPreferences, DB or Network (currently from SharedPreferences).
I want to update views with these values in onViewCreated
. But they update with empty values before a coroutine in onCreateView
finishes. How to wait until the coroutine finishes in main thread?
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
...
GlobalScope.launch(Dispatchers.Main) {
val job = GlobalScope.launch(Dispatchers.IO) {
val task = async(Dispatchers.IO) {
settingsInteractor.getStationSearchCountry().let {
countryName = it.name
}
settingsInteractor.getStationSearchRegion().let {
regionName = it.name
}
}
task.await()
}
job.join()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
country.updateCaption(countryName)
region.updateCaption(regionName)
}
UPDATE (20-05-2019)
Currently I don't use onViewCreated
. I write code in onCreate
and onCreateView
. In onCreateView
I access views this way: view.country.text = "abc"
and so on.
When we launch a coroutine in Kotlin using launch we are returned the resulting Job . We can wait for the coroutine to finish by calling join() on the Job.
cancelAndJoin() // cancels the job and waits for its completion println("main: Now I can quit.") like the following example shows: val startTime = System. currentTimeMillis() val job = launch(Dispatchers.
They tend to slow down when there are a lot of tasks being run, which is why using coroutines help to speed things up. Threads run tasks in serial, while coroutines runs many tasks at the same time.
Coroutines can be executed concurrently using a multi-threaded dispatcher like the Dispatchers.
It's better to use async
instead of launch
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html.
Create lateinit val dataLoad: Deferred<Pair<String, String>
. init it as dataLoad = GlobalScope.async(Dispatchers.Defailt) {}
in onCreateView
or earlier.
launch
new coroutine in UI scope in onViewCreated
. Wait for result in that coroutine val data = dataLoad.await()
and then apply data to ui
In your case you don't need to use GlobalScope
as a coroutine context (you can but it is not recommended as per docs). You need a local scope:
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
@Override
public void onDestroy() {
super.onDestroy();
job.cancel()
}
Also your fragment should implement CoroutineScope
and to use Dispatchers.Main
in Android add dependency to app's build.gradle:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
The code to wait until the coroutine finishes:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
launch {
val operation = async(Dispatchers.IO) {
settingsInteractor.getStationSearchCountry().let {
countryName = it.name
}
settingsInteractor.getStationSearchRegion().let {
regionName = it.name
}
}
operation.await() // wait for result of I/O operation without blocking the main thread
// update views
country.updateCaption(countryName)
region.updateCaption(regionName)
}
}
EDIT:
In Activity
or Fragment
you can use lifecycleScope
instead of implementing CoroutineScope
:
lifecycleScope.launch { ... }
To use lifecycleScope
add next line to dependencies of the app's build.gradle file:
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION"
At the time of writing final LIFECYCLE_VERSION = "2.3.0-alpha05"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With