Reputation: 28793
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.
Upvotes: 13
Views: 19284
Reputation: 30655
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"
Upvotes: 5
Reputation: 28793
Use runBlocking. In this case you can block UI thread until coroutine completion. Of course, you are to be sure it will be fast enough (less than 3 sec) to not raise ANR.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
...
val (countryName, regionName) = runBlocking {
// Switch to background thread and continue blocking UI thread.
withContext(Dispatchers.IO) {
val countryName = settingsInteractor.getStationSearchCountry().let {
it.name
}
val regionName = settingsInteractor.getStationSearchRegion().let {
it.name
}
countryName to regionName
}
}
// Assign data after runBlocking has finished.
view.country_name.text = countryName
view.region_name.text = regionName
return view
}
I didn't test it, because use similar code in onBackPressed
. When I quit a fragment (or an activity) I should write some data in DB. If running too late, onDestroy
will be called and coroutines will be finished. In this case data won't be written to DB. So, I have to stop UI thread, write to DB in background thread, then return to UI. runBlocking
works well.
Upvotes: -1
Reputation: 3454
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
Upvotes: 6