在 Android 单向数据流中更新不可变视图状态值
Updating Immutable View State Values in Android Unidirectional Data Flow
问题
我希望在 Android ViewModel (VM) 中重构不可变视图状态的值,以便执行以下操作:
- 在不复制整个视图状态的情况下干净地更新 VM 中的视图状态
- 保持视图状态数据对观察更新的视图不可变
我已经构建了一个 Android 单向数据流 (UDF) 模式,使用 LiveData 来更新在视图中观察到的 VM 中的视图状态变化。
参见:Android Unidirectional Data Flow with LiveData — 2.0
完整示例代码:Coinverse Open App
实施
现有的实现使用嵌套的 LiveData。
- 一个 LiveData
val
用于在 VM 中存储视图状态
- 视图状态属性的嵌套 LiveData 不可变
val
s
// Stored as viewState LiveData val in VM
data class FeedViewState(
val contentList: LiveData<PagedList<Content>>
val anotherAttribute: LiveData<Int>)
视图状态是在 VM 的 init{...}
中创建的。
然后,为了更新视图状态,必须使用给定的属性复制和更新它,因为它是不可变的 val
。如果属性是可变的,它可以在 VM 中没有 copy
的情况下干净地重新分配。但是,不可变对于确保视图不会无意中更改 val
.
很重要
class ViewModel: ViewModel() {
val viewState: LiveData<FeedViewState> get() = _viewState
private val _viewState = MutableLiveData<FeedViewState>()
init {
_viewState.value = FeedViewState(
contentList = getContentList(...)
anotherAttribute = ...)
}
override fun swipeToRefresh(event: SwipeToRefresh) {
_viewState.value = _viewState.value?.copy(contentList = getContentList(...))
}
}
我不确定 "nested LiveData" 是否合适。当我们使用任何事件驱动的设计实现(LiveData
、RxJava
、Flow
)时,我们通常需要假设离散数据事件是不可变的,并且对这些事件的操作是纯函数式的。不可变并不等同于只读 (val
)。不可变的意思是不可变的。它应该是时不变的,并且在任何情况下都应该以完全相同的方式工作。这就是为什么我对数据 class 中有 LiveData
或 ArrayList
成员感到奇怪的原因之一,无论它们是否被定义为只读。
应避免嵌套流的另一个技术原因:几乎不可能正确观察它们。每次通过外部流发出新的数据事件时,开发人员必须确保在观察新的内部流之前删除内部订阅,否则会导致各种问题。当开发人员需要手动取消订阅时,拥有生命周期感知观察者有什么意义?
几乎所有场景下,嵌套流都可以转化为一层流。在你的情况下:
class ViewModel: ViewModel() {
val contentList: LiveData<PagedList<Content>>
val anotherAttribute: LiveData<Int>
private val swipeToRefreshTrigger = MutableLiveData<Boolean>(true)
init {
contentList = Transformations.switchMap(swipeToRefreshTrigger) {
getContentList(...)
}
anotherAttribute = ...
}
override fun swipeToRefresh(event: SwipeToRefresh) {
swipeToRefreshTrigger.postValue(true)
}
}
关于 PagedList
的注释:
PagedList
也是可变的,但我想这是我们不得不忍受的。 PagedList
用法是另一个话题,所以我不会在这里讨论它。
使用 Kotlin StateFlow - 2020 年 7 月 21 日更新
与其使用 LiveData 有两个状态 class,一个是私有和可变的,另一个 public 和不可变的,使用 Kotlin 协程 1.3.6 release 可以更新 StateFlow 值ViewModel,并通过接口方法在视图 activity/fragment 中呈现。
参见:Android Model-View-Intent with Kotlin Flow
删除嵌套的 LiveData,创建状态 类 - 2/11/20
方法:将不可变的 LiveData 状态和效果存储在视图状态中,并在 public 可访问的 ViewModel 中查看效果 class。
视图状态和视图效果属性可以是直接在 VM 中的 LiveData 值。但是,我想将视图状态和效果组织到单独的 class 中,以便视图知道它观察的是视图状态还是视图效果。
class FeedViewState(
_contentList: MutableLiveData<PagedList<Content>>,
_anotherAttribute: MutableLiveData<Int>
) {
val contentList: LiveData<PagedList<Content>> = _contentList
val anotherAttribute: LiveData<Int> = _anotherAttribute
}
视图状态是在 VM 中创建的。
class ViewModel: ViewModel() {
val feedViewState: FeedViewState
private val _contentList = MutableLiveData<PagedList<Content>>()
private val _anotherAttribute = MutableLiveData<Int>()
init {
feedViewState = FeedViewState(_contentList, _anotherAttribute)
}
...
fun updateContent(){
_contentList.value = ...
}
fun updateAnotherAttribute(){
_anotherAttribute.value = ...
}
}
然后,将在 activity/fragment.
中观察视图状态属性
class Fragment: Fragment() {
private fun observeViewState() {
feedViewModel.feedViewState.contentList(viewLifecycleOwner){ pagedList: PagedList<Content> ->
adapter.submitList(pagedList)
}
feedViewModel.feedViewState.anotherAttribute(viewLifecycleOwner){ anotherAttribute: Int ->
//TODO: Do something with other attribute.
}
}
}
问题
我希望在 Android ViewModel (VM) 中重构不可变视图状态的值,以便执行以下操作:
- 在不复制整个视图状态的情况下干净地更新 VM 中的视图状态
- 保持视图状态数据对观察更新的视图不可变
我已经构建了一个 Android 单向数据流 (UDF) 模式,使用 LiveData 来更新在视图中观察到的 VM 中的视图状态变化。
参见:Android Unidirectional Data Flow with LiveData — 2.0
完整示例代码:Coinverse Open App
实施
现有的实现使用嵌套的 LiveData。
- 一个 LiveData
val
用于在 VM 中存储视图状态 - 视图状态属性的嵌套 LiveData 不可变
val
s
// Stored as viewState LiveData val in VM
data class FeedViewState(
val contentList: LiveData<PagedList<Content>>
val anotherAttribute: LiveData<Int>)
视图状态是在 VM 的 init{...}
中创建的。
然后,为了更新视图状态,必须使用给定的属性复制和更新它,因为它是不可变的 val
。如果属性是可变的,它可以在 VM 中没有 copy
的情况下干净地重新分配。但是,不可变对于确保视图不会无意中更改 val
.
class ViewModel: ViewModel() {
val viewState: LiveData<FeedViewState> get() = _viewState
private val _viewState = MutableLiveData<FeedViewState>()
init {
_viewState.value = FeedViewState(
contentList = getContentList(...)
anotherAttribute = ...)
}
override fun swipeToRefresh(event: SwipeToRefresh) {
_viewState.value = _viewState.value?.copy(contentList = getContentList(...))
}
}
我不确定 "nested LiveData" 是否合适。当我们使用任何事件驱动的设计实现(LiveData
、RxJava
、Flow
)时,我们通常需要假设离散数据事件是不可变的,并且对这些事件的操作是纯函数式的。不可变并不等同于只读 (val
)。不可变的意思是不可变的。它应该是时不变的,并且在任何情况下都应该以完全相同的方式工作。这就是为什么我对数据 class 中有 LiveData
或 ArrayList
成员感到奇怪的原因之一,无论它们是否被定义为只读。
应避免嵌套流的另一个技术原因:几乎不可能正确观察它们。每次通过外部流发出新的数据事件时,开发人员必须确保在观察新的内部流之前删除内部订阅,否则会导致各种问题。当开发人员需要手动取消订阅时,拥有生命周期感知观察者有什么意义?
几乎所有场景下,嵌套流都可以转化为一层流。在你的情况下:
class ViewModel: ViewModel() {
val contentList: LiveData<PagedList<Content>>
val anotherAttribute: LiveData<Int>
private val swipeToRefreshTrigger = MutableLiveData<Boolean>(true)
init {
contentList = Transformations.switchMap(swipeToRefreshTrigger) {
getContentList(...)
}
anotherAttribute = ...
}
override fun swipeToRefresh(event: SwipeToRefresh) {
swipeToRefreshTrigger.postValue(true)
}
}
关于 PagedList
的注释:
PagedList
也是可变的,但我想这是我们不得不忍受的。 PagedList
用法是另一个话题,所以我不会在这里讨论它。
使用 Kotlin StateFlow - 2020 年 7 月 21 日更新
与其使用 LiveData 有两个状态 class,一个是私有和可变的,另一个 public 和不可变的,使用 Kotlin 协程 1.3.6 release 可以更新 StateFlow 值ViewModel,并通过接口方法在视图 activity/fragment 中呈现。
参见:Android Model-View-Intent with Kotlin Flow
删除嵌套的 LiveData,创建状态 类 - 2/11/20
方法:将不可变的 LiveData 状态和效果存储在视图状态中,并在 public 可访问的 ViewModel 中查看效果 class。
视图状态和视图效果属性可以是直接在 VM 中的 LiveData 值。但是,我想将视图状态和效果组织到单独的 class 中,以便视图知道它观察的是视图状态还是视图效果。
class FeedViewState(
_contentList: MutableLiveData<PagedList<Content>>,
_anotherAttribute: MutableLiveData<Int>
) {
val contentList: LiveData<PagedList<Content>> = _contentList
val anotherAttribute: LiveData<Int> = _anotherAttribute
}
视图状态是在 VM 中创建的。
class ViewModel: ViewModel() {
val feedViewState: FeedViewState
private val _contentList = MutableLiveData<PagedList<Content>>()
private val _anotherAttribute = MutableLiveData<Int>()
init {
feedViewState = FeedViewState(_contentList, _anotherAttribute)
}
...
fun updateContent(){
_contentList.value = ...
}
fun updateAnotherAttribute(){
_anotherAttribute.value = ...
}
}
然后,将在 activity/fragment.
中观察视图状态属性class Fragment: Fragment() {
private fun observeViewState() {
feedViewModel.feedViewState.contentList(viewLifecycleOwner){ pagedList: PagedList<Content> ->
adapter.submitList(pagedList)
}
feedViewModel.feedViewState.anotherAttribute(viewLifecycleOwner){ anotherAttribute: Int ->
//TODO: Do something with other attribute.
}
}
}