【问题标题】:Correct implementation of the View in android MVVMandroid MVVM中View的正确实现
【发布时间】:2020-11-21 13:03:54
【问题描述】:

所以在 MVVM 架构中,即使在 google 示例中,我们也可以看到如下内容:

class CharacterListActivity :BaseActivity() {
    
    val ViewModel: MainViewModel 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.getData()   // Bad!!!
        ...
        viewModel.state.observe(this) { state ->
            when(state) {             // handling state is not views job
                Success -> { navigatetoNextPage() }  // navigating is not views job
                Progress -> { showProgress() }         
                NetworkError -> { ShowSnackbar(viewModel.error) }   // I,m not sure about this one either
                Error -> { showErrorDialog(viewModel.error)         
            }
    }

我们知道,任何架构都有自己的规则,这些规则使代码随着时间的推移可测试、可维护和可扩展。 根据WikipediaMicrosoft docs 在MVVM 模式中,这是视图:

视图是用户在屏幕上看到的结构、布局和外观。[6]它显示模型的表示并接收用户与视图的交互(点击、键盘、手势等),并通过数据绑定(属性、事件回调等)将这些处理转发给视图模型。被定义为链接视图和视图模型。

每个视图都在 XAML 中定义,具有不包含业务逻辑的有限代码隐藏。但是,在某些情况下,代码隐藏可能包含 UI 逻辑,用于实现动画等视觉行为。

XAML 是 Xamarin 的东西,所以现在让我们回到我们的代码:
在这里,由于activity 决定如何处理state,活动就像在MVC 中的Controller 一样工作,但是活动应该是View,视图只是应该做UI 逻辑。
该活动甚至告诉ViewModel 获取数据。这又不是View 的工作。

请注意,告诉代码中的其他模块做什么不是视图的工作。这使视图充当控制器。视图应该通过来自 ViewModel 的回调来处理其状态。 View 应该只是告诉ViewModelonClick() 这样的事件。

由于 ViewModel 无权访问 View,因此无法显示对话框或直接在应用中导航!

那么在不违反架构规则的情况下,有什么替代方法可以做到这一点?我是否应该为 ViewModel 中的任何生命周期事件提供函数,例如 viewModel.onCreate?viewModel.onStart?导航或显示对话框呢?

为了记录我没有混淆 mvc 和 mvvm,我是说这种模式可以,推荐购买 google。

这不是基于意见的,当然任何人都可以拥有自己的任何架构实现,但必须始终遵循规则以实现加班可维护性。

我可以为你一一命名这段代码中的违规行为:

1) UI 不负责获取数据,UI 只需要将事件告诉 ViewModel。

2) UI 不负责处理状态,这正是它在这里所做的。更一般地说,UI 不应包含任何非 UI 逻辑。

3) UI 不负责在屏幕之间导航

【问题讨论】:

  • 您可以在 ViewModel 的 init 块中使用 getData()
  • 是的,但情况并非总是如此

标签: android kotlin mvvm viewmodel android-viewmodel


【解决方案1】:

活动甚至告诉 ViewModel 获取数据。这又不是 View 的工作。

正确。数据获取应该由 ViewModel.init 触发,或者更准确地说是激活响应式数据源(由 LiveData 建模,用 onActive/onInactive 包装所述响应式源)。

如果 fetch 必须作为 create 的结果发生(这不太可能),则可以使用 DefaultLifecycleObserver 使用 Jetpack Lifecycle API 创建自定义生命周期感知组件来完成。

参考https://stackoverflow.com/a/59109512/2413303

由于 ViewModel 无权访问 View,因此无法显示对话框或直接在应用中导航!

您可以使用自定义生命周期感知组件,例如EventEmitter(或here)将一次性事件从 ViewModel 发送到 View。

您还可以参考一种更高级的技术,其中不只是一个事件,而是以作为事件发送的 lambda 表达式的形式向下发送实际命令,当它可用时将由 Activity 处理。

参考https://medium.com/@Zhuinden/simplifying-jetpack-navigation-between-top-level-destinations-using-dagger-hilt-3d918721d91e

typealias NavigationCommand = NavController.() -> Unit

@ActivityRetainedScoped
class NavigationDispatcher @Inject constructor() {
    private val navigationEmitter: EventEmitter<NavigationCommand> = EventEmitter()
    val navigationCommands: EventSource<NavigationCommand> = navigationEmitter

    fun emit(navigationCommand: NavigationCommand) {
        navigationEmitter.emit(navigationCommand)
    }
}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var navigationDispatcher: NavigationDispatcher

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        navigationDispatcher.navigationCommands.observe(this) { command ->
            command.invoke(Navigation.findNavController(this, R.id.nav_host))
        }
    }
}

class LoginViewModel @ViewModelInject constructor(
    private val navigationDispatcher: NavigationDispatcher,
    @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    fun onRegisterClicked() {
        navigationDispatcher.emit { 
            navigate(R.id.logged_out_to_registration)
        }
    }
}

如果不使用 Hilt,则可以使用 Activity 范围的 ViewModel 和自定义 AbstractSavedStateViewModelFactory 子类来完成等效操作。

【讨论】:

  • 感谢您的回答,不过有几个问题,............ 1) ViewModel 中的 getData() 函数应该从网络中获取数据。我把你提到的答案加红了,我认为这不是我的情况,基本上我希望能够在onStart()onCreate 或任何地方获取数据。假设返回类型是来自 Rx 的 Single&lt;String&gt; 或简单的回调........ 2) 这是什么“自定义 AbstractSavedStateViewModelFactory”?只是一个保存状态的 ViewModelFactory ?
  • 1.) 你绝对可以创建一些 UseCase 类或这样公开一个 Single,你可以从 ViewModel 调用它,但你仍然应该尝试从 LiveData.onActive 调用它以允许响应数据获取 2.) 参考 github.com/Zhuinden/jetpack-navigation-ftue-sample/blob/master/…medium.com/@Zhuinden/…
猜你喜欢
  • 2023-03-23
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 2012-11-11
  • 2017-12-06
  • 2017-06-20
  • 2018-06-14
  • 1970-01-01
相关资源
最近更新 更多