android - 使用 Android 的 MVVM 模式时从一个 fragment 导航到另一个 fragment

标签 android kotlin android-jetpack android-mvvm

  • 我正在使用 MVVM 模式创建应用程序。我正在使用 导航图 管理我的应用程序中的 fragment ,根据推荐的方法,我们不必将 UI 逻辑放入 Activity/fragment 但在 查看模型 .
  • 所以我的问题是如何从一个 fragment 导航到另一个 fragment 。我知道这可以使用 navController.navigate(R.id.action_here) 直接在 fragment 内部完成但是我将如何在按钮按下时处理来自 ViewModel 的导航?

  • 我的代码:

    IntroViewModel.kt


    class IntroViewModel : ViewModel() {
    
        fun onBtn1Pressed(view: View) {
            Log.d(IntroViewModel::class.java.simpleName, ": onBtn1Pressed")
        }
    
        fun onBtn2Pressed(view: View) {
            Log.d(IntroViewModel::class.java.simpleName, ": onBtn2Pressed ")
        }
    }
    

    IntroFragment.kt:


    class IntroFragment : Fragment() {
    
        private lateinit var viewModel: IntroViewModel
        private lateinit var navController: NavController
        lateinit var introBinding: IntroFragmentBinding
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            introBinding = DataBindingUtil.inflate(inflater, R.layout.intro_fragment, container, false)
            viewModel = ViewModelProviders.of(this).get(IntroViewModel::class.java)
            introBinding.introModel = viewModel
            return introBinding.root;
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            navController = Navigation.findNavController(view)
    
        }
    }
    

    intro_fragment.xml:



    <data>
        <variable
            name="introModel"
            type="example.com.viewmodel.IntroViewModel" />
    </data>
    
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:padding="@dimen/padding_16dp"
        tools:context=".fragments.IntroFragment">
    
        <TextView
            android:id="@+id/txt_"
            style="@style/TextAppearance.MaterialComponents.Headline5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="Choose one " />
    
        <com.google.android.material.button.MaterialButton
            android:id="@+id/btn_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/txt_"
            android:onClick="@{introModel::onBtn1Pressed}"
            android:layout_marginTop="@dimen/margin_8dp"
            android:text="Btn1" />
    
        <com.google.android.material.button.MaterialButton
            android:id="@+id/btn_2"
            style="@style/Widget.MaterialComponents.Button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{introModel::onBtn2Pressed}"
            android:layout_below="@id/btn_1"
            android:layout_alignStart="@id/btn_1"
            android:layout_alignEnd="@id/btn_1"
            android:layout_marginTop="@dimen/margin_8dp"
            android:text="Btn2" />
    
    </RelativeLayout>
    

    最佳答案

    ViewModel 内部导航这意味着您需要一个与 MVVM 概念背道而驰的 View 实例。相反,请使用 LiveData向您的 fragment 指示它需要导航到下一个目的地。您可以使用以下 Event类(来自 Google 的 architecture-samples 之一)以确保导航只触发一次。

    open class Event<out T>(private val content: T) {
    
        @Suppress("MemberVisibilityCanBePrivate")
        var hasBeenHandled = false
            private set // Allow external read but not write
    
        /**
         * Returns the content and prevents its use again.
         */
        fun getContentIfNotHandled(): T? {
            return if (hasBeenHandled) {
                null
            } else {
                hasBeenHandled = true
                content
            }
        }
    
        /**
         * Returns the content, even if it's already been handled.
         */
        fun peekContent(): T = content
    }
    

    与此 Observer 一起使用:
    /**
     * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has
     * already been handled.
     *
     * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled.
     */
    class EventObserver<T>(private val onEventUnhandledContent: (T) -> Unit) : Observer<Event<T>> {
        override fun onChanged(event: Event<T>?) {
            event?.getContentIfNotHandled()?.let {
                onEventUnhandledContent(it)
            }
        }
    }
    

    这是你的LiveData :
    private val _openTaskEvent = MutableLiveData<Event<String>>()
    val openTaskEvent: LiveData<Event<String>> = _openTaskEvent
    

    最后你可以这样观察它:
    viewModel.openTaskEvent.observe(this, EventObserver {
        //Do your navigation here
    })
    

    关于android - 使用 Android 的 MVVM 模式时从一个 fragment 导航到另一个 fragment ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60622645/

    相关文章:

    android - 如何从 USB 数码相机 (RAW) 中提取图像?

    android - Dexguard 发布 apk 不显示 UI automator 中元素的资源 ID

    android - Kotlin 主要和次要构造函数问题

    android - Kotlin 无法识别类型参数

    android - 拥有多个 View 模型(每个 fragment 大约一个)是一种不好的做法吗?

    java - Android 从 AsyncTask 访问 MainActivity

    javascript - 使用 Gmail 帐户登录我的应用程序?

    java - 函数式编程 : How to carry on the context for a chain of validation rules

    android - DataSource.Invalidate() 之后新的 PagedList 只有一页

    Android ViewPager2,上一项和下一项变暗处理