Android ViewModel技术疑问:数据类是否应暴露LiveData属性?
首先咱们先理清问题根源:你点击按钮后TextView不更新,是因为SelectedUser是LiveData<User>,但你修改的是User内部的普通name字段——LiveData只会在自身的value被替换时才会通知观察者,修改内部字段不会触发这个更新机制,所以UI感知不到变化。
接下来逐个解答你的问题:
1. 数据类是否需要将自身字段重新暴露为LiveData?
完全不需要。数据类的设计初衷是作为纯数据载体,把每个字段都改成LiveData会让它变得臃肿,还会破坏它的单一职责。咱们应该在ViewModel层通过LiveData持有整个数据类对象,通过更新LiveData的value来触发UI更新,而不是修改数据类本身。
2. 原普通字段该如何处理?是直接替换还是采用其他命名规则保留?
推荐保留原普通字段,通过ViewModel层的LiveData重赋值来触发更新,这里给你两种可行方案:
- 方案一(最常用):MutableLiveData持有User对象,修改后重赋值
把ViewModel里的SelectedUser改成私有MutableLiveData加公开LiveData的模式,修改字段后重新赋值触发通知:
这种方案完全不需要修改数据类的字段,保留它的纯数据属性。// ViewModel内部 private val _selectedUser = MutableLiveData<User>() val selectedUser: LiveData<User> = _selectedUser // 对外暴露不可变的LiveData fun changeSelectedUserName() { _selectedUser.value?.let { user -> user.name = "foo" _selectedUser.value = user // 关键:重新赋值触发LiveData通知观察者 } } - 方案二(不推荐):将数据类字段改为LiveData
如果你非要让字段本身可观察,也可以把name改成MutableLiveData<String>,但这样数据类就不再是纯POJO,和Retrofit配合会出问题(Retrofit无法直接解析LiveData类型的字段),而且代码冗余,所以不建议这么做。
3. Retrofit适配的命名规则:最少代码同时满足接口与LiveData使用
Retrofit需要的是纯数据类(POJO),字段名要和接口返回的JSON字段匹配,这里的关键是:数据类字段保持和接口JSON对应的命名(或用@SerializedName映射),ViewModel层用LiveData持有整个数据类对象,不需要修改数据类的命名规则,就能同时适配Retrofit和LiveData。
举个实际例子:
假设接口返回的JSON是:
{ "user_id": 1, "user_name": "bar" }
那么数据类可以这么写(用@SerializedName解决命名差异):
import com.google.gson.annotations.SerializedName data class User( @SerializedName("user_id") val id: Int, @SerializedName("user_name") var name: String // 保留普通字段,方便修改 )
然后ViewModel里正常用LiveData持有:
// 从Retrofit获取数据并更新LiveData fun fetchUser(userId: Int) { viewModelScope.launch { val response = apiService.getUser(userId) _selectedUser.value = response.body() } }
这样既满足Retrofit的JSON解析要求,又能通过ViewModel的LiveData驱动UI更新,代码量最少,也符合MVVM的分层设计。
总结一下核心思路:数据类负责承载数据,ViewModel负责持有可观察的数据对象并处理业务逻辑,UI只观察ViewModel的LiveData即可。
内容的提问来源于stack exchange,提问作者RonaldPaguay

