83

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

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

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

wherebundleBundle(当然)的一个实例。如您所见SelectionFragment,我们无法访问可以放置回调的位置。问题是,如何使用导航架构组件接收结果?

4

9 回答 9

92

他们在 2.3.0-alpha02 版本中为此添加了修复程序。

如果从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)
}
于 2020-03-19T12:57:40.783 回答
69

从 Fragment KTX 1.3.6 开始,Android 支持在 Fragment 之间或 Fragment 与 Activity 之间传递数据。它类似于startActivityForResult逻辑。

这是导航组件的示例。你可以在这里阅读更多关于它的信息

构建.gradle

implementation "androidx.fragment:fragment-ktx:1.3.6"

片段A.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)
    }
}

片段B.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()
        }
    }
}    
于 2020-05-20T18:33:18.920 回答
28

根据谷歌:你应该尝试使用共享 ViewModel。查看以下来自 Google 的示例:

Shared ViewModel将包含共享数据并且可以从不同的 Fragment 访问。

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.
        });
    }
}
于 2018-06-16T20:45:07.593 回答
28

使用这些扩展功能

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 将继续将最后一个结果返回给任何新的 Observer 实例。

更多信息在官方指南中。

于 2020-06-11T09:15:23.873 回答
3

随着@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)
}
于 2020-06-02T21:42:47.867 回答
0
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)
}
于 2021-12-16T16:33:42.143 回答
0

我创建了一个类似于 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 的那么简单,但它是通用的,可以处理以下情况:

  1. 一个父母的几个孩子
  2. 结果是任何实现的类Parcelable
  3. 对话目的地

请参阅github上的实现或查看解释其工作原理的文章

于 2021-07-24T16:29:53.620 回答
-2

我建议您使用NavigationResult库,它是 JetPack 导航组件的附加组件,可让您navigateUp使用 Bundle。我还在 Medium 上写了一篇关于此的博客文章。

于 2019-12-10T09:09:38.007 回答
-2

只是其他答案的替代品......

以 MutableShareFlow 为核心的 EventBus 是共享对象(例如:repo)和此处描述的观察者

看起来事情正在远离 LiveData 并朝着 Flow 方向发展。

值得一看。

于 2021-04-10T18:00:03.460 回答