如何使用导航架构组件从片段中获取结果?

Posted

技术标签:

【中文标题】如何使用导航架构组件从片段中获取结果?【英文标题】:How to get a result from fragment using Navigation Architecture Component? 【发布时间】:2018-11-18 03:41:15 【问题描述】:

假设我们有两个片段:MainFragmentSelectionFragment。第二个是为选择一些对象而构建的,例如一个整数。从第二个片段接收结果有不同的方法,如回调、总线等。

现在,如果我们决定使用导航架构组件来导航到第二个片段,我们可以使用以下代码:

NavHostFragment.findNavController(this).navigate(R.id.action_selection, bundle)

其中bundleBundle 的一个实例(当然)。如您所见,无法访问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 不能与&lt;dialog&gt; 目标一起正常工作(请参阅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)... 如果我们没有视图模型怎么办? @CarsonH​​olzheimer 这里的父母是什么? @CarsonH​​olzheimer 你说的问题在这里解决了***.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 并朝着流方向发展。

值得一看。

【讨论】:

以上是关于如何使用导航架构组件从片段中获取结果?的主要内容,如果未能解决你的问题,请参考以下文章

在 FAB 单击时导航到片段(导航架构组件)

如何使用导航架构组件查找子片段

导航架构组件 - 对话框片段

使用导航架构组件添加(而不是替换)片段

关于android导航架构组件的问题

底部应用栏在使用片段导航时向上/向下滑动(导航架构组件)