但组名和位置应该只在最初更新,而不是在用户列表更新时更新。
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.
}
}