Android Fragments 需要使用handler.postDelayed从DialogFragment返回,使用带有布尔参数的导航组件返回

mrzz3bfm  于 9个月前  发布在  Android
关注(0)|答案(1)|浏览(45)

我正在使用导航组件来启动和关闭ErrorDialog。我注意到,当我尝试重新打开重试流上的错误对话框时,我得到一个错误,即无法找到该操作。它在对话框第一次启动时起作用。

findNavController().navigate(R.id.action_secondFragment_to_dialog_navigation)

我还注意到我必须按两次后退按钮才能返回到原始页面。我认为问题在于DialogFragment没有作为当前目的地被删除.我是对的。
当我第二次使用调试检查器时,您想要启动它,它显示当前导航目的地是对话框片段。
使用一个带有小延迟的延迟& postDelayed来解决问题。仅仅在主线程上使用帖子是不起作用的;你需要一个小的延迟。
然而,这似乎不是正确的解决方案。这真的是一个比赛条件还是我做错了什么?
下面是相关代码。
这里是我从第二个片段中调度DialogFragment的地方

private fun showErrorDialog() {
        if (!isRemoving && isVisible) {
            findNavController().navigate(R.id.action_secondFragment_to_dialog_navigation)
        }
    }

这里是我设置结果并退出对话框的地方(在ErrorDialogFragment中)

private fun navigateBackWithResult(result: Boolean) {
        findNavController()
            .previousBackStackEntry
            ?.savedStateHandle
            ?.set(DIALOG_RESULT, result)
    }

这里是我从ErrorDialog中监听结果的地方

private fun setupErrorDialogListener() {
        findNavController()
            .currentBackStackEntry
            ?.savedStateHandle
            ?.getLiveData<Boolean>(ErrorDialogFragment.DIALOG_RESULT)
            ?.observe(viewLifecycleOwner) {
                Log.d("dialog returned in observer with $it", TAG)
                it?.let {
                    //Solves an issue with navigation component after retrying dialog.  My guess
                    //is that you need to wait for error dialog fragment transaction to complete and be removed from backstack
                    //Does not work without the small delay.
                    Handler(Looper.getMainLooper()).postDelayed(
                        {
                            if (it) binding.vm?.refreshCalled() else Log.d("user declined to refresh", TAG)
                        },
                        50,
                    )
                }.whenNull { Log.e("failed to get user response from fragment through navigator", TAG) }
            }
    }

下面是启动对话框的片段的导航XML

<fragment
        android:id="@+id/secondFragment"
        android:name="ca.xyz.android.fragments.secondFragment"
        android:label="@string/second_fragment"
        tools:layout="@layout/second_fragment" />
        <action
            android:id="@+id/action_secondFragment_to_dialog_navigation"
            app:destination="@id/dialog_navigation"
            app:launchSingleTop="false"
            app:popUpTo="@id/secondFragment">
            <argument
                android:name="DIALOG_RESULT"
                app:argType="boolean" />
        </action>
    </fragment>

(我不认为singleTop标志有任何作用,我会删除它,看看会发生什么,但这是不相关的。
下面是对话框片段的相关导航XML

<navigation android:id="@+id/dialog_navigation"
        app:startDestination="@id/error_dialog">
        <dialog
            android:id="@+id/error_dialog"
            android:label="@string/step_two_title"
            android:name="ca.xyz.android.fragments.ErrorDialogFragment">
            <argument
                android:name="DIALOG_RESULT"
                app:argType="boolean" />
        </dialog>
    </navigation>
2ul0zpep

2ul0zpep1#

当你使用这种回调函数时(也就是currentBackStackEntrypreviousBackStackEntry作为活动数据),如果对话框是打开的,在前面的片段中,viewLifecycleOwner仍然是活动的。这意味着,当您设置结果时,它会同步传输,这意味着错误对话框仍然可见。动作action_secondFragment_to_dialog_navigation必须在secondFragment位于前台时从secondFragment调用,也就是在它位于顶部片段时。如果它被认为是非活动的,则会引发此异常。
Handler().postDelayed()方法之所以有效,是因为您给了给予时间让错误对话框消失到第二个片段,由于导航更改,第二个片段成为顶部片段,从而允许它再次触发错误片段。简单的post不起作用,因为它可能需要多个帧才能使片段完全消失(考虑到动画等),或者将其注册到导航器中。
这可以被认为是一个竞争条件(但不完全是因为它不是两个线程,而是两个独立的片段/对话框),你试图在对话框消失之前再次调用它。
您可以:

  • 将对话框移动到同一个图中,并使用它的ID(a.k.a navigate(R.id.error_dialog)),这将不会使用该操作并解决崩溃问题(这可能会造成一个小的内存泄漏,请小心),
  • 等待对话框消失,或者,你的片段成为导航器中的顶部片段,通过添加一个临时的navigator.addOnDestinationChangedListener并检测它何时被添加,之后,再次调用对话框。

相关问题