Alan
Alan

Reputation: 9481

LiveData and Coroutines - Property must be initialized or abstract

I am trying to use LiveData and Coroutines together in MVVM, and I may be missing something simple.

class WeatherViewModel (
    private val weatherRepository: ForecastRepository
) : ViewModel() {

    var weather: LiveData<Weather>;

    /**
     * Cancel all coroutines when the ViewModel is cleared.
     */
    @ExperimentalCoroutinesApi
    override fun onCleared() {
        super.onCleared()
        viewModelScope.cancel()
    }


    init {
        viewModelScope.launch {
            weather = weatherRepository.getWeather()
        }

    }

}

But I am getting Property must be initialized or be abstract on assigning the weather in the init function. I am assuming this is the case because I am using coroutines viewModelScope.launch.

override suspend fun getWeather(): LiveData<Weather> {
    return withContext(IO){
       initWeatherData()
       return@withContext weatherDao.getWeather()
    }
}

How do I fix this?

Upvotes: 5

Views: 1972

Answers (4)

daneejela
daneejela

Reputation: 14233

In Kotlin, by default, every property must be initialized (even the nullable types with null).

You can initialize your property directly in the declaration or within init block.

Problem with your code is that the launch function can keep on going after your init block is done, and the compiler knows this. So, it's telling you - don't count on it.

As previously said, you can use lateinit to state that you'll initialize your property later, if you're sure it will happen before you're gonna use it.

Upvotes: 0

Sergio
Sergio

Reputation: 30735

You can declare weather property as lateinit:

private lateinit var weather: LiveData<String>

Or make it nullable:

private var weather: LiveData<String>? = null

If you're sure that the property will be initialized before you first use it use lateinit otherwise make it nullable.

Upvotes: 2

Laurence
Laurence

Reputation: 1676

weather must be initialized with instantiation of the class because you have not said that it can be null, and you are not using the lateinit keyword (which you should not in this case).

launch is an async coroutine call that returns immediately, but will be executed at some point in the future. This means that your init block completes and returns without weather being initialized.

Use runBlocking instead. This will block until you have the result in the init block and so guarantee that weather is not null on instantiation. Something like:

init {
    weather = runBlocking {
        weatherRepository.getWeather()
    }
}

You can pass which ever coroutine context dispatcher to runBlocking as well.

Or - stick with the coroutine, but join in the init block like:

init {
    val job = viewModelScope.launch {
        weather = weatherRepository.getWeather()
    }
    job.join()
} 

Upvotes: -1

Luca Nicoletti
Luca Nicoletti

Reputation: 2427

Change the signature as follow:

var weather =  MutableLiveData<Weather>();

Plus, you should return just a Weather object and not a LiveData<> so you should change the signature of getWeather() to return : Weather.

Upvotes: 0

Related Questions