android MVVM 中视图的正确实现

Correct implementation of the View in android MVVM

所以在 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)         
            }
    }

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

the view is the structure, layout, and appearance of what a user sees on the screen.[6] It displays a representation of the model and receives the user's interaction with the view (clicks, keyboard, gestures, etc.), and it forwards the handling of these to the view model via the data binding (properties, event callbacks, etc.) that is defined to link the view and view model.

each view is defined in XAML, with a limited code-behind that does not contain business logic. However, in some cases, the code-behind might contain UI logic that implements visual behavior such as animations.

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

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

之类的事件

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

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

The Record 我没有混淆 mvc 和 mvvm,我是说这个模式确实推荐购买 google.

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

我可以为你一一列举出这段代码中的违规行为:

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

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

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

the activity even tells the ViewModel to get data. this is again not the View's job.

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

如果获取必须作为创建的结果发生,这不太可能,那么可以使用 DefaultLifecycleObserver 使用 Jetpack 生命周期 API 创建自定义生命周期感知组件来完成。

参考

since ViewModel doesn't have access to View, it can't show a dialog or navigate through the app directly!

您可以使用自定义生命周期感知组件,例如 EventEmitter (or 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 子类来完成等效操作。