在 Android 单向数据流中更新不可变视图状态值

Updating Immutable View State Values in Android Unidirectional Data Flow

问题

我希望在 Android ViewModel (VM) 中重构不可变视图状态的值,以便执行以下操作:

  1. 在不复制整个视图状态的情况下干净地更新 VM 中的视图状态
  2. 保持视图状态数据对观察更新的视图不可变

我已经构建了一个 Android 单向数据流 (UDF) 模式,使用 LiveData 来更新在视图中观察到的 VM 中的视图状态变化。

参见:Android Unidirectional Data Flow with LiveData — 2.0

完整示例代码:Coinverse Open App

实施

现有的实现使用嵌套的 LiveData。

// 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" 是否合适。当我们使用任何事件驱动的设计实现(LiveDataRxJavaFlow)时,我们通常需要假设离散数据事件是不可变的,并且对这些事件的操作是纯函数式的。不可变并不等同于只读 (val)。不可变的意思是不可变的。它应该是时不变的,并且在任何情况下都应该以完全相同的方式工作。这就是为什么我对数据 class 中有 LiveDataArrayList 成员感到奇怪的原因之一,无论它们是否被定义为只读。

应避免嵌套流的另一个技术原因:几乎不可能正确观察它们。每次通过外部流发出新的数据事件时,开发人员必须确保在观察新的内部流之前删除内部订阅,否则会导致各种问题。当开发人员需要手动取消订阅时,拥有生命周期感知观察者有什么意义?

几乎所有场景下,嵌套流都可以转化为一层流。在你的情况下:

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.
        }
    }
}