有关MutableLiveData传null的问题


#1

我有一个有关MutableLiveData的问题,求解答:
最近我在搞Android的MVVM架构,使用ViewModel和LiveData来完成。然后我发现一个很严重的问题。

问题描述

在Java中,Livedata如果使用LiveData<T>()的方法初始化的话,它的value值默认是null。所以如果使用Kotlin去看LiveData的实例化对象liveData的时候,liveData.value这个值的属性是T?
但是在这个时候,如果使用postValue(null)或者setValue(null)这两个方法,它会调用Observer里的onChange()方法,然后会直接报错。

具体场景

现在,我们新建一个ViewModel对象,并在里面建一个LiveData对象:

// MyViewModel.kt
// AndroidViewModel和ViewModel差不多,但是多一个Application
class MyViewModel(app: Application) : AndroidViewModel(app) {
    private val _user = MutableLiveData<User>()
    // 从sharedPreference中获取用户序号
    private val _currentId = MutableLiveData<Int>().apply {
        value = getApplication<MyApp>().prefs.getInt("current_user", -1)
    }
    // 我的Room Database的DAO
    private val dao = getApplication<MyApp>().appDatabase.userDao()
    val user: LiveData<User>
        get() = _user
    init {
        _user.value = dao,find(_currentId.value!!)
    }
}

DAO里面也很简单,就是一个SQL语句

@Dao
interface UserDao {
    @Query("select * from user where id = :id")
    fun find(id: Int): User?
}

User的定义就是username和password,还有一个主键id

@Entity(tableName = "user")
data class User(
        @PrimaryKey(autoGenerate = true)
        var id: Int = 0,
        var name: String = "",
        var password: String = ""
)

可以看到,在应用第一次启动的时候,数据库、sharedPreference刚刚创建,这个User?必定是空。而_user.value也一定是null,此时

// MainFragment
// 为了方便进行ViewBinding,我自己建了一个BasicFragment,与本问题无关所以我就不写具体实现了
class MyFragment : BasicFragment<FragmentMyBinding>() {
    // 初始化ViewModel
    val viewModel by lazy {
        ViewModelProvider(requireActivity())[MyViewModel::class.java]
    }
...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.user.observe(this@MyFragment) {
            binding.userTextView.text = it.name
        }
    }
}

如果你使用的是Android Studio,那么上述代码中的itUser!类型的。这些在编译之前和编译之后不会报任何错误,但是运行的时候会遇到User.getName() on null object reference这个错误。可见,无论传入任何一个值,包括null在内,调用postValue(value)或者setValue(value)都会使得Observer调用onChange函数。
这个问题出现的原因很奇怪,讲道理这个问题难道不应该在编译前就应该发现的吗?User!到底是可空对象还是不可空对象???
如果理性分析一下的话,应该是Java中这个value值并没有说明它是@NotNull,但是Kotlin里这个it看上去是非空的对象。这个差异可能是导致这个问题的出现。

进阶问题

还有一个应用场景,建立一个枚举类

enum class Mode: Serializable {
    IPV4, IPV6, BOTH
}

在ViewModel中建立一个LiveData

class MyViewModel(app: Application) : AndroidViewModel(app) {
...

    private val _mode = MutableLiveData<Mode>()
    val mode: LiveData<Mode>
        get() = _mode
    init {
        ...
        _mode.value = Mode.IPV4
    }
...
}

在你遍历的时候Observer里和外面却不一样

viewModel.mode.observe(this) {
    when (it) {
        Mode.IPV6 -> doSomething()
        Mode.IPV4 -> doSomething2()
        else -> doSomething3()
}

你会发现最后一个必须是else,而不能是Mode.BOTH,即使Mode是一个枚举类。
如果你写Mode.BOTH,IDE会提示你这个it可能是空对象。这里我就不理解了,这个it到底是什么??

提前感谢各位的光顾和解答,这是我第一次在这里发帖子:>


#2

其实本质上还是跟JavaAPI交互时空安全的问题
由于Java没有加 Nullable 或者 NotNull 注解,Kotlin 没法推断出来这个方法返回值到底是空还是非空的,所以就是 T! 类型,这个感叹号就是这种情况下会出现
对于 T! 类型,到底是 T? 还是 T ,这个交给开发者判断,为了安全你可以把 T! 全当 T? 用,除非一些 API 你确定没有空就可以直接 T。为啥 Kotlin 没有强制性要求把 T! 当 T? 我猜是为了方便调 Java 的库,最后那个 when 也只是给出 warning 而不是直接报错。


#3

顺便。对于 T! 如果你确定是非空/可空的,你可以直接 it as T/T? 来确定
比如,比如最后那个when写成这样就不会有 warning

when (it as Mode) {

#4

感谢感谢!!


京ICP备16022265号-2 Kotlin China 2017 - 2018