【问题标题】:Update part of UI using MVI on Android在 Android 上使用 MVI 更新部分 UI
【发布时间】:2021-11-01 08:39:30
【问题描述】:
  1. 我对 MVI 的理解是,模型创建状态,视图处理它。视图总是从模型中获取完整状态,这意味着赋予视图的每个状态每次都包含视图每个部分的信息。我是否理解正确?

  2. 鉴于上述 1 是正确的,如果我每次都获得完整状态,我如何只更新视图的一小部分?示例:模型由类型 Group 和 User 组成。组包含许多用户、名称和位置。编辑组时,我想显示名称、位置和可能组成员(用户)的列表,并为每个用户提供复选框。要求说用户列表可以在组编辑过程中发生变化;可以删除用户,新用户可以加入(从数据库中添加或删除),同时显示视图。因此,用户列表需要在编辑组时能够更新。但是组名和位置应该只在最初更新,而不是在更新用户列表时更新。例如。我不想多次调用名称 textview 的 .setText() 。如何按照 MVI 视图状态原则实现这一点?

【问题讨论】:

    标签: android android-architecture


    【解决方案1】:

    但组名和位置应该只在最初更新,而不是在用户列表更新时更新。

    MVI 代表模型-视图-意图。本质上,您的视图是用单一状态建模的。您的视图通过收听此整个状态的输出来更新。每当用户通过“点击”之类的 Intent 与 View 进行交互时,ViewModel(或任何业务逻辑类)只会更新所述状态的受影响字段。

    您通过此要求在这里要求的内容不符合此模式。如果您想满足上面引用的要求,您需要为群组名称、群组位置和成员列表提供单独的状态。

    然后,每当列表发生更改时,您只需将更新推送到列表,它的任何消费者都会被触发并相应地更新 UI,而无需打扰名称和位置。

    MVI 解决方案

    视图总是从模型中获得完整状态,这意味着赋予视图的每个状态每次都包含视图每个部分的信息。

    是的,视图将始终获取整个状态,这没关系。 MVI 的重要部分是 ViewModel 只会更新已更改的字段。

    如何按照 MVI 视图状态原则实现这一点?

    我们需要创建一个代表视图状态的模型。假设您使用的是 Kotlin,我们可以这样做:

    data class GroupsViewState(
        val numOfUsers: Int,
        val location: Location,
        val members: List<User>
    )
    
    data class Location(
        val x: Float,
        val y: Float
    )
    
    data class User(
        val isChecked: Boolean
    )
    

    然后,我们需要定义我们的意图。本质上,用户可以对上面的 View 状态执行哪些操作?这是一个例子:

    sealed class GroupsIntent {
        data class UpdateNumOfUsers(val number: Int) : GroupsIntent()
        data class AddUser(val user: User) : GroupsIntent()
        data class DeleteUser(val user: User) : GroupsIntent()
        data class ToggleUser(val user: User): GroupsIntent()
        // etc.
    }
    

    Kotlin 为其数据类提供了一个强大的运算符,这对于 MVI 的工作至关重要:copy

    此运算符允许您更新数据类中的某些字段

    因此,在您的 ViewModel 中,您可以执行以下操作:

    class GroupsViewModel : ViewModel() {
    
        // Internal MutableLiveData that we'll update based on Intents
        private val _groupsState = MutableLiveData<GroupsViewState>()    
        // Exposes an immutable LiveData to consumers and triggers updates
        // whenever the Mutable counterpart is updated.
        val groupsState: LiveData<GroupsViewState> get() = _groupsState
    
        // This is the appeal of MVI
        // All Intents come through one funnel function and get relayed accordingly
        fun processIntent(intent: GroupsIntent) {
            when (intent) {
                is AddUser -> addUser(intent.user)
                is DeleteUser -> deleteUser(intent.user)
                is ToggleUser -> toggleUser(intent.user)
            }
        }
    
        // Copies the mutated list with new User appended to the View state
        // **without** touching the other fields
        private fun addUser(user: User) {
            val currentViewState = getCurrentState()
            val mutableMembers = currentViewState.members.toMutableList()
            _groupsState.value = currentViewState.copy(members = mutableMembers.add(user))
        }
    
        private fun deleteUser(user: User) {
            // Similar approach to above, but with delete logic
        }
    
        private fun toggleUser(user: User) {
            // Toggle logic similar to above
        }
    
        private fun getCurrentViewState() = groupsState.value
    }
    

    然后,在 UI 层:

    class GroupsActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_groups)
            // Bind to your Views
            // etc.
            setupObervers()
            bindIntents()
        }
    
        // Subscribe to your ViewState's LiveData and
        // update the View according to the changes
        private fun setupObservers() {
            viewModel.groupsState.observe(this) { viewState ->
                numOfUsersTextView.text = viewState.numOfUsers
                locationView.value = viewState.location // I made 'value' up
                membersListViewWidget = viewState.members
            }
        }
    
        // Bind your UI widget listeners to processIntent() with
        // corresponding values
        private fun bindIntents() {
            numOfUsersText.setOnClickListener { num ->
                viewModel.processIntent(UpdateNumOfUsers(num)
            }
            // etc.
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-04-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-15
      • 2014-02-16
      • 1970-01-01
      • 2018-04-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多