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

问题描述 投票:0回答:1

我正在使用导航组件来启动和关闭错误对话框。我注意到,当我尝试在重试流程上重新打开错误对话框时,我收到一条错误消息,指出无法找到该操作。它在第一次启动对话框时起作用。

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

我还注意到我必须按两次后退按钮才能返回原始页面。我认为问题在于 DialogFragment 没有作为当前目的地被删除......我是对的。

当我第二次使用调试检查器时,你想启动它,它显示当前导航目的地是对话框片段。

使用 Handler 和 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>
android android-fragments android-architecture-navigation android-navigation android-navigation-graph
1个回答
0
投票

当您使用这种回调(又名

currentBackStackEntry
previousBackStackEntry
并作为实时数据)时,如果对话框打开,则在上一个片段中
viewLifecycleOwner
仍然处于活动状态。这意味着,当您设置结果时,它会同步传输,这意味着错误对话框仍然可见。动作
action_secondFragment_to_dialog_navigation
必须在
secondFragment
位于前台(即位于顶部片段时)调用。如果被认为是不活动的,就会抛出这个异常。

Handler().postDelayed()
方法之所以有效,是因为您给了错误对话框消失到第二个片段的时间,第二个片段由于导航更改而成为顶部片段,从而允许它再次触发错误片段。简单的
post
不起作用,因为片段完全消失(考虑动画等)或在导航器中注册可能需要超过一帧的时间。

这可以被视为竞争条件(但不完全是因为它不是两个线程,而是两个单独的片段/对话框),您试图在对话框消失之前再次调用它。

您可以:

  • 将对话框移至同一个图表并使用其 ID(又名
    navigate(R.id.error_dialog)
    ),这不会使用该操作并解决崩溃问题(这可能会造成小的内存泄漏,请小心),
  • 等待对话框消失,或者您的片段成为导航器内的顶部片段,通过添加临时
    navigator.addOnDestinationChangedListener
    并检测何时添加,之后再次调用对话框。
© www.soinside.com 2019 - 2024. All rights reserved.