如何在不绑定到 ViewModel (MVVM) 中的 UI 的情况下使用 android 导航?

How to use android navigation without binding to UI in ViewModel (MVVM)?

我正在使用在 Google I/O 2018 年发布的 android 导航,似乎我可以通过绑定到某个视图或使用 NavHost 来使用它从片段中获取它。但我需要的是根据多种条件从我的第一个片段导航到 ViewModel 的另一个特定视图。对于 ViewModel,我扩展了 AndroidViewModel,但我不明白下一步该怎么做。我无法将 getApplication 转换为 Fragment/Activity,也无法使用 NavHostFragment。我也不能只将导航绑定到 onClickListener,因为 startFragment 只包含一个 ImageView。如何从 ViewModel 导航?

class CaptionViewModel(app: Application) : AndroidViewModel(app) {
private val dealerProfile = DealerProfile(getApplication())
val TAG = "REGDEB"


 fun start(){
    if(dealerProfile.getOperatorId().isEmpty()){
        if(dealerProfile.isFirstTimeLaunch()){
            Log.d(TAG, "First Time Launch")
            showTour()
        }else{
            showCodeFragment()
            Log.d(TAG, "Show Code Fragment")

        }
    }
}

private fun showCodeFragment(){
    //??
}

private fun showTour(){
    //??
}

}

我的片段

class CaptionFragment : Fragment() {
private lateinit var viewModel: CaptionViewModel
private val navController by lazy { NavHostFragment.findNavController(this) }

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    viewModel = ViewModelProviders.of(this).get(CaptionViewModel::class.java)
    return inflater.inflate(R.layout.fragment_caption, container, false)
}


override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    viewModel.start()

}

}

我想在 ViewModel 中保留导航逻辑

How can I navigate from ViewModel?

答案是请不要。 ViewModel 旨在存储和管理 UI 相关数据。

新答案

在我之前的回答中,我说过我们不应该从 ViewModel 导航,原因是因为要导航,ViewModel 必须引用 Activities/Fragments,我相信(可能不是最好的,但是我仍然相信它)从来都不是一个好主意。

但是,在 Google 推荐的应用程序架构中,它提到我们 应该从模型 驱动 UI。后来我想,他们是什么意思?

所以我检查了 "android-architecture" 中的样本,我发现了一些有趣的方法 Google 是如何做到的。

请在此处查看:todo-mvvm-databinding

事实证明,他们确实 从模型 驱动 UI。但是怎么办?

  1. 他们创建了一个界面 TasksNavigator,基本上只是一个导航界面。
  2. 然后在 TasksViewModel 中,他们拥有对 TaskNavigator 的引用,因此他们可以驱动 UI 而无需直接引用 Activities / Fragments。
  3. 最后,TasksActivity 实现了 TasksNavigator 以提供每个导航操作的详细信息,然后将 navigator 设置为 TasksViewModel。

我可以推荐两种方法。

  1. 使用 LiveData 进行通信并告诉片段进行导航。
  2. 创建一个名为 Router 的 class,它可以包含您的导航逻辑和对片段或导航组件的引用。 ViewModel 可以与路由器通信 class 进行导航。

您可以使用可选的自定义枚举类型并观察视图中的变化:

enum class NavigationDestination {
    SHOW_TOUR, SHOW_CODE_FRAGMENT
}

class CaptionViewModel(app: Application) : AndroidViewModel(app) {
    private val dealerProfile = DealerProfile(getApplication())
    val TAG = "REGDEB"

    private val _destination = MutableLiveData<NavigationDestination?>(null)
    val destination: LiveData<NavigationDestination?> get() = _destination
    
    fun setDestinationToNull() {
        _destination.value = null
    }
    

    fun start(){
        if(dealerProfile.getOperatorId().isEmpty()){
            if(dealerProfile.isFirstTimeLaunch()){
                Log.d(TAG, "First Time Launch")
                _destination.value = NavigationDestination.SHOW_TOUR
            }else{
                _destination.value = NavigationDestination.SHOW_CODE_FRAGMENT
                Log.d(TAG, "Show Code Fragment")

            }
        }
    }
}

然后在您的视图中观察 viewModel 目标变量:

viewModel.destination.observe(this, Observer { status ->
            if (status != null) {
                viewModel.setDestinationToNull()
                status?.let {
                    when (status) {
                        NavigationDestination.SHOW_TOUR -> {
                            // Navigate to your fragment
                        }
                        NavigationDestination.SHOW_CODE_FRAGMENT -> {
                            // Navigate to your fragment
                        }
                    }
                })
            }

如果您只有一个目的地,您可以只使用布尔值而不是枚举。