我正在使用导航组件来启动和关闭错误对话框。我注意到,当我尝试在重试流程上重新打开错误对话框时,我收到一条错误消息,指出无法找到该操作。它在第一次启动对话框时起作用。
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>
当您使用这种回调(又名
currentBackStackEntry
和 previousBackStackEntry
并作为实时数据)时,如果对话框打开,则在上一个片段中 viewLifecycleOwner
仍然处于活动状态。这意味着,当您设置结果时,它会同步传输,这意味着错误对话框仍然可见。动作 action_secondFragment_to_dialog_navigation
必须在 secondFragment
位于前台(即位于顶部片段时)调用。如果被认为是不活动的,就会抛出这个异常。
Handler().postDelayed()
方法之所以有效,是因为您给了错误对话框消失到第二个片段的时间,第二个片段由于导航更改而成为顶部片段,从而允许它再次触发错误片段。简单的 post
不起作用,因为片段完全消失(考虑动画等)或在导航器中注册可能需要超过一帧的时间。
这可以被视为竞争条件(但不完全是因为它不是两个线程,而是两个单独的片段/对话框),您试图在对话框消失之前再次调用它。
您可以:
navigate(R.id.error_dialog)
),这不会使用该操作并解决崩溃问题(这可能会造成小的内存泄漏,请小心),navigator.addOnDestinationChangedListener
并检测何时添加,之后再次调用对话框。