关于 MVI,我了解的一件事是模型创建状态,视图处理它。视图总是从模型中获得完整的状态,这意味着赋予视图的每个状态都包含视图每个部分的信息,每次。我是否正确理解了它?
鉴于上述 1 是正确的,如果我每次都获得完整状态,我如何只更新视图的一小部分?示例:模型由类型 Group 和 User 组成。组包含许多用户、名称和位置。编辑组时,我想显示名称、位置和可能的组成员(用户)列表,并为每个用户显示复选框。要求说用户列表可以在组编辑过程中发生变化;可以删除用户,新用户可以加入(从数据库中添加或删除),同时显示视图。因此,用户列表需要在编辑组时能够更新。但是组名和位置应该只在最初更新,而不是在更新用户列表时更新。例如,我不想多次调用名称 textview 的 .setText()。如何按照 MVI 视图状态原则实现这一点?
1 回答
但是组名和位置应该只在最初更新,而不是在更新用户列表时更新。
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.
}
}