Reputation: 7055
tldr; how to init variable with asynchronous data
Learning kotlin and android, simple example from bignerdranch book. Beginner question here.
A data class is displayed on the app.
private var characterData = CharacterGenerator.generate()
Here .generate() just get a static element. (other functions used to display)
I implemented, so now on click on the button the data are fetched from an api (it works)
generateButton.setOnClickListener {
GlobalScope.launch(Dispatchers.Main) {
characterData = fetchCharacterData().await()
displayCharacterData()
}
}
Question is how to implement the initial data fetch and assign it to characterData ?
private var characterData = CharacterGenerator.generate() // we want to replace generate() with the api call results
Obviously this won't work either
private var characterData = fetchCharacterData().await()
But also this won't work
private var characterData = GlobalScope.launch(Dispatchers.Main) {
characterData = fetchCharacterData().await()
displayCharacterData()
}
So what is the right way to assign/initialize charactedData with data from api ?
edit:
fun fetchCharacterData(): Deferred<CharacterGenerator.CharacterData> {
return GlobalScope.async {
val apiData = URL(CHARACTER_DATA_API).readText()
CharacterGenerator.fromApiData(apiData)
}
}
fromApiData return an dataClass instance of CharacterData type
Upvotes: 0
Views: 1075
Reputation: 93834
Since you should not block the main thread, but character data is not available initially, you should start with empty character data. I don't know what your character data class looks like, but you should compose it in a way that allows for it to be empty, or just allow it to be nullable. Your UI code should be able to handle this case, either by just not filling in the data, or by showing a Spinner initially.
This is a pattern you will see over and over for any application that has to fetch data asynchronously. If it's very quick, you don't need to bother with a spinner.
Side note: Avoid GlobalScope (article is by the design lead for Kotlin).
Second side note: It's a little clumsy to have a function that returns a Deferred. The standard pattern with coroutines would be to write it as a suspend function, and use withContext
internally to make sure it doesn't block when called from a Main dispatcher:
suspend fun fetchCharacterData(): CharacterGenerator.CharacterData = withContext(Dispatchers.IO) {
val apiData = URL(CHARACTER_DATA_API).readText()
CharacterGenerator.fromApiData(apiData)
}
generateButton.setOnClickListener {
lifecycleScope.launch {
characterData = fetchCharacterData()
displayCharacterData()
}
}
Upvotes: 1