如何使用导航架构组件从片段中获取结果?
Posted
技术标签:
【中文标题】如何使用导航架构组件从片段中获取结果?【英文标题】:How to get a result from fragment using Navigation Architecture Component? 【发布时间】:2018-11-18 03:41:15 【问题描述】:假设我们有两个片段:MainFragment
和 SelectionFragment
。第二个是为选择一些对象而构建的,例如一个整数。从第二个片段接收结果有不同的方法,如回调、总线等。
现在,如果我们决定使用导航架构组件来导航到第二个片段,我们可以使用以下代码:
NavHostFragment.findNavController(this).navigate(R.id.action_selection, bundle)
其中bundle
是Bundle
的一个实例(当然)。如您所见,无法访问SelectionFragment
,我们可以在其中进行回调。问题是,如何通过导航架构组件接收结果?
【问题讨论】:
让SelectionFragment
更新共享的ViewModel
(直接或间接),订阅MainFragment
以了解ViewModel
的变化。
前提是你使用的是ViewModel
,与Navigation Component无关。
正确。它们旨在协同工作,Google is indicating that a shared ViewModel
is the recommended way to communicate between fragments when using the Navigation library.
我认为您应该将其发布为答案。
使用图范围的共享视图模型***.com/questions/55137338/…
【参考方案1】:
他们在 2.3.0-alpha02 版本中添加了fix for this。
如果从 Fragment A 导航到 Fragment B 并且 A 需要来自 B 的结果:
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<Type>("key")?.observe(viewLifecycleOwner) result ->
// Do something with the result.
如果在Fragment B上并且需要设置结果:
findNavController().previousBackStackEntry?.savedStateHandle?.set("key", result)
我最终为此创建了两个扩展:
fun Fragment.getNavigationResult(key: String = "result") =
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>(key)
fun Fragment.setNavigationResult(result: String, key: String = "result")
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, result)
【讨论】:
这应该被接受为正确的答案。如此简单,完美地完成工作 也许它对有导航组件和对话框的人有用***.com/a/62054347/7968334 java.lang.IllegalArgumentException: Can't put value with type class com.experienceapi.auth.models.LogonModel into saved state 。有什么想法吗? 确保实现 Parcelable 接口以保存自定义对象。 当心currentBackStackEntry
不能与<dialog>
目标一起正常工作(请参阅docs),您需要使用getBackStackEntry()
和目标ID 然后【参考方案2】:
由于 Fragment KTX 1.3.6 android 支持在 Fragment 之间或 Fragment 与 Activity 之间传递数据。类似于startActivityForResult
的逻辑。
这是一个导航组件的例子。你可以阅读更多关于它here
build.gradle
implementation "androidx.fragment:fragment-ktx:1.3.6"
FragmentA.kt
class FragmentA : Fragment()
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
// Step 1. Listen for fragment results
setFragmentResultListener(FragmentB.REQUEST_KEY) key, bundle ->
// read from the bundle
// Step 2. Navigate to Fragment B
findNavController().navigate(R.id.fragmentB)
FragmentB.kt
class FragmentB : Fragment()
companion object
val REQUEST_KEY = "FragmentB_REQUEST_KEY"
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
buttonA.setOnClickListener view ->
// Step 3. Set a result
setFragmentResult(REQUEST_KEY, bundleOf("data" to "button clicked"))
// Step 4. Go back to Fragment A
findNavController().navigateUp()
【讨论】:
你的解决方案存在这个问题:***.com/q/63669116/3248593 非常有趣和简单的决定,我需要从子片段返回数据 @MortezaRastgoo 我认为这个问题可以通过 viewmodel 或任何持久化数据策略来解决 不适合我,片段结果监听器从未调用过,也不知道为什么【参考方案3】:使用这些扩展功能
fun <T> Fragment.getNavigationResult(key: String = "result") =
findNavController().currentBackStackEntry?.savedStateHandle?.get<T>(key)
fun <T> Fragment.getNavigationResultLiveData(key: String = "result") =
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<T>(key)
fun <T> Fragment.setNavigationResult(result: T, key: String = "result")
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, result)
因此,如果您想将结果从 Fragment B 发送到 Fragment A
内部片段B
setNavigationResult(false, "someKey")
内部片段A
val result = fragment.getNavigationResultLiveData<Boolean>("someKey")
result.observe(viewLifecycleOwner) booleanValue-> doSomething(booleanValue)
重要提示
在 Fragment B 中你需要设置结果 (setNavigationResult()) 处于启动或恢复状态(在 onStop() 或 onDestroy() 之前), 否则 previousBackStackEntry 将已经为空。
重要提示 #2
如果您只想处理一次结果,则必须调用 SavedStateHandle 上的 remove() 以清除结果。如果你不 删除结果,LiveData 会继续返回最后一个 结果到任何新的观察者实例。
更多信息in the official guide。
【讨论】:
轻松、简单、快速!非常感谢……太棒了! 这个答案很好 我尝试使用 savedStateHandle 并能够设置带有结果的键,但我从未在观察者中收到更新。我使用的是 2.3.4 版本... @shadygoneinsane 我遇到了同样的问题。当我尝试调试它时,我发现在navController.previousBackStackEntry
调用的生命周期中非常重要。例如onStop()
或onDestroy()
中的previousBackStackEntry
已经是null
。所以你需要在之前设置结果。根据this documentation:getPreviousBackStackEntry
- 返回后堆栈上的上一个可见条目,如果后堆栈的可见条目少于两个,则返回 null。
请注意,如果。 “result.observe(viewLifecycleOwner) booleanValue-> doSomething(booleanValue)” 无法解析为布尔值,请将函数放在括号内,如下所示:“observe(viewLifecycleOwner, Observer booleanValue -> )”。快乐编码g【参考方案4】:
根据谷歌:you should try to use shared ViewModel。查看以下来自 Google 的示例:
Shared ViewModel 将包含共享数据并且可以从不同的片段访问。
public class SharedViewModel extends ViewModel
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item)
selected.setValue(item);
public LiveData<Item> getSelected()
return selected;
更新 ViewModel 的 MasterFragment:
public class MasterFragment extends Fragment
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item ->
model.select(item);
);
使用共享 ViewModel 的DetailsFragment:
public class DetailFragment extends Fragment
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, item ->
// Update the UI.
);
【讨论】:
问题在于您将视图模型的范围限定为getActivity()
的活动。这意味着它永远不会被清除(使用额外的内存,并且当您稍后导航回这些片段时可能会导致意外结果,并且会显示陈旧的数据)。你应该改用ViewModelProviders.of(parent)...
。
如果我们没有视图模型怎么办?
@CarsonHolzheimer 这里的父母是什么?
@CarsonHolzheimer 你说的问题在这里解决了***.com/questions/55137338/…
我不会说解决了。它们是一些不太理想的解决方法。一般来说,共享视图模型并不总是一个好主意。对于简单地将结果返回到以前的活动,它们比即将推出的返回结果的 API 更难阅读和构建。【参考方案5】:
对@LeHaine 的回答稍加改进,就可以使用这些方法导航2.3.0-alpha02
及以上
fun <T> Fragment.getNavigationResult(key: String) =
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<T>(key)
fun <T> Fragment.setNavigationResult(result: T, key: String)
findNavController().previousBackStackEntry?.savedStateHandle?.set(key, result)
【讨论】:
【参考方案6】:我创建了一个类似于 LeHaine 的答案的包装函数,但它处理的情况更多。
将结果从孩子传递给父母:
findNavController().finishWithResult(PickIntervalResult.WEEKLY)
从父级中的子级获取结果:
findNavController().handleResult<PickIntervalResult>(
viewLifecycleOwner,
R.id.navigation_notifications, // current destination
R.id.pickNotificationIntervalFragment // child destination
) result ->
binding.textNotifications.text = result.toString()
我的包装器不像 LeHaine 的那么简单,但它是通用的,可以处理以下情况:
-
一个父母的几个孩子
结果是任何实现
Parcelable
的类
对话目的地
查看github 上的实现或查看an article that explains how it works。
【讨论】:
【参考方案7】:fun <Type> BaseNavigationActivity<*,*,*>.getNavigationResult(key : String, @IdRes viewId: Int) =
this.findNavController(viewId).currentBackStackEntry?.savedStateHandle?.getLiveData<Type>(key)
fun <Type> BaseNavigationActivity<*,*,*>.setNavigationResult(result: Type, key: String, @IdRes viewId: Int)
this.findNavController(viewId).previousBackStackEntry?.savedStateHandle?.set<Type>(key, result)
【讨论】:
欢迎来到 Stack Overflow。当代码附有解释时,它会更有帮助。 Stack Overflow 是关于学习的,而不是提供 sn-ps 来盲目复制和粘贴。请编辑您的答案并解释它如何回答所提出的具体问题。见【如何回答】***.com/questions/how-to-answer)【参考方案8】:我建议您使用 NavigationResult 库,它是 JetPack 导航组件的附加组件,可让您使用 Bundle 访问 navigateUp
。
我还在 Medium 上写了一个 blog post 来讨论这个问题。
【讨论】:
我不推荐这个库,因为它高度依赖继承。这迟早会限制你的努力。 TL;DR 支持继承组合:medium.com/@rufuszh90/…【参考方案9】:只是其他答案的替代方案......
以 MutableShareFlow 为核心的 EventBus 在共享对象(例如:repo)和 here 中描述的观察者
看起来事情正在远离 LiveData 并朝着流方向发展。
值得一看。
【讨论】:
以上是关于如何使用导航架构组件从片段中获取结果?的主要内容,如果未能解决你的问题,请参考以下文章