François Richard
François Richard

Reputation: 7055

Kotlin android - assign await result to variable

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

Answers (1)

Tenfour04
Tenfour04

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

Related Questions