【问题标题】:LiveData and Coroutines - Property must be initialized or abstractLiveData 和 Coroutines - 属性必须被初始化或抽象
【发布时间】:2019-09-06 21:34:10
【问题描述】:

我正在尝试在 MVVM 中同时使用 LiveData 和 Coroutines,我可能会遗漏一些简单的东西。

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()
        }

    }

}

但是我在init 函数中分配weather 时得到Property must be initialized or be abstract。 我假设是这种情况,因为我正在使用协程viewModelScope.launch

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

我该如何解决这个问题?

【问题讨论】:

    标签: android kotlin kotlin-coroutines android-livedata coroutine


    【解决方案1】:

    更改签名如下:

    var weather =  MutableLiveData<Weather>();
    

    另外,您应该只返回一个 Weather 对象而不是 LiveData 所以你应该把getWeather()的签名改成返回: Weather

    【讨论】:

    • 听起来不太对劲。这意味着我会将Weather 对象分配给MutableLiveData&lt;Weather&gt;。我只需要 Room 或 Retrofit 在存储库中提供的 LiveData
    • 不,您将把 Room 或 Retrofit 提供的 Weather 对象分配给 weather.value
    • 但是从LiveData 中取出值并将其放入MutableLiveData 将破坏使用LiveData 的目的。我正在使用数据绑定,并希望值随着值的变化而自动更新。
    • 您应该以这种方式使用LiveData进行视图更新,而不是用于ViewModel/Presenter--Room/Retrofit交互
    【解决方案2】:

    weather 必须通过类的实例化进行初始化,因为您没有说它可以为 null,并且您没有使用 lateinit 关键字(在这种情况下您不应该使用该关键字)。

    launch 是一个异步协程调用,会立即返回,但会在未来的某个时间点执行。这意味着您的 init 块完成并返回,而 weather 没有被初始化。

    请改用runBlocking。这将一直阻塞,直到您在 init 块中获得结果,因此保证在实例化时天气不为空。比如:

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

    您也可以将任何协程上下文调度程序传递给runBlocking

    或者 - 坚持使用协程,但像这样加入 init 块:

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

    【讨论】:

    • 用这个方法返回之前不会阻塞主线程吗?
    • @Alan 这个类被实例化的线程将被阻塞,直到对象被实例化(包括它的所有非空变量)。是的。如果您想立即返回并将weather 类型设置为LiveData&lt;Weather&gt;? 并在以后的某个阶段设置该字段,但我怀疑在设置该变量之前它是无用的,然后您将不得不进行空检查每次你想使用它。 Kotlin 是 null 安全的。
    • @Alan 您正在尝试做有冲突的事情 - 有一个不为空的变量,并且还在对象构造时异步实例化它。如果weatherRepository.getWeather() 不是必须在协程上下文中调用的挂起函数,那么只需执行weather = weatherRepository.getWeather()。我的理解是,在拥有该实例之前,您的班级无法做任何需要做的事情。
    【解决方案3】:

    您可以将weather 属性声明为lateinit

    private lateinit var weather: LiveData<String>
    

    或者让它nullable

    private var weather: LiveData<String>? = null
    

    如果您确定该属性将在您首次使用它之前被初始化,请使用lateinit,否则将其设为可为空

    【讨论】:

      【解决方案4】:

      在 Kotlin 中,默认情况下,每个属性都必须被初始化(即使是可以为 null 的类型)。

      您可以直接在声明中或在 init 块中初始化您的属性。

      您的代码的问题是启动功能可以在您的 init 块完成后继续运行,并且编译器知道这一点。所以,它在告诉你——不要指望它。

      如前所述,您可以使用 lateinit 声明您稍后会初始化您的属性,如果您确定它会在您使用它之前发生。

      【讨论】:

        猜你喜欢
        • 2016-02-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-11
        • 1970-01-01
        相关资源
        最近更新 更多