0
  1. 关于 MVI,我了解的一件事是模型创建状态,视图处理它。视图总是从模型中获得完整的状态,这意味着赋予视图的每个状态都包含视图每个部分的信息,每次。我是否正确理解了它?

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

4

1 回答 1

0

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

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

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

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

MVI 解决方案

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

是的,视图总是会得到整个状态,这没关系。MVI 的重要部分是 ViewModel 只会更新已更改的字段。

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

我们需要创建一个模型来代表您的 View 的状态。假设您使用的是 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.
    }
}
于 2022-02-05T05:57:22.823 回答