How to get a result from fragment using Navigation Architecture Component?

AndroidAndroid Architecture-ComponentsAndroid JetpackAndroid Architecture-Navigation

Android Problem Overview


Let's say that we have two fragments: MainFragment and SelectionFragment. The second one is build for selecting some object, e.g. an integer. There are different approaches in receiving result from this second fragment like callbacks, buses etc.

Now, if we decide to use Navigation Architecture Component in order to navigate to second fragment we can use this code:

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

where bundle is an instance of Bundle (of course). As you can see there is no access to SelectionFragment where we could put a callback. The question is, how to receive a result with Navigation Architecture Component?

Android Solutions


Solution 1 - Android

They have added a fix for this in the 2.3.0-alpha02 release.

If navigating from Fragment A to Fragment B and A needs a result from B:

findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<Type>("key")?.observe(viewLifecycleOwner) {result ->
    // Do something with the result.
}

If on Fragment B and need to set the result:

findNavController().previousBackStackEntry?.savedStateHandle?.set("key", result)

I ended up creating two extensions for this:

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)
}

Solution 2 - Android

Since Fragment KTX 1.3.6 Android supports passing data between fragments or between fragments and activities. It's similar to startActivityForResult logic.

Here is an example with Navigation Component. You can read more about it 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()
        }
    }
}    

Solution 3 - Android

Use these extension functions

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)
}

So if you want to send result from Fragment B to fragment A

Inside Fragment B

setNavigationResult(false, "someKey")

Inside Fragment A

val result = fragment.getNavigationResultLiveData<Boolean>("someKey")
result.observe(viewLifecycleOwner){ booleanValue-> doSomething(booleanValue)

Important note > In the Fragment B you need to set the result (setNavigationResult()) > in the started or resumed state (before onStop() or onDestroy()), > otherwise previousBackStackEntry will be already null.

Important note #2 > If you’d only like to handle a result only once, you must call > remove() on the SavedStateHandle to clear the result. If you do not > remove the result, the LiveData will continue to return the last > result to any new Observer instances.

More information in the official guide.

Solution 4 - Android

According to Google: you should try to use shared ViewModel. Check below example from Google:

Shared ViewModel that will contain shared data and can be accessed from different fragments.

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;
    }
}

MasterFragment that updates ViewModel:

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);
        });
    }
}

DetailsFragment that uses shared ViewModel:

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.
        });
    }
}

Solution 5 - Android

with a little improvement of @LeHaine 's answer, you can use these methods for navigation 2.3.0-alpha02 and above

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)
}

Solution 6 - Android

I created a wrapper function that's similar to LeHaine's answer, but it handles more cases.

To pass a result to a parent from a child:

findNavController().finishWithResult(PickIntervalResult.WEEKLY)

To get result from a child in a parent:

findNavController().handleResult<PickIntervalResult>(
    viewLifecycleOwner,
    R.id.navigation_notifications, // current destination
    R.id.pickNotificationIntervalFragment // child destination
    ) { result ->
        binding.textNotifications.text = result.toString()
}

My wrapper isn't so simple as LeHaine's one, but it is generic and handles cases as:

  1. A few children for one parent
  2. Result is any class that implements Parcelable
  3. Dialog destination

See the implementation on the github or check out an article that explains how it works.

Solution 7 - Android

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)
}

Solution 8 - Android

I would suggest you to use NavigationResult library which is an add-on for JetPack's Navigation Component and lets you to navigateUp with Bundle. I've also wrote a blog post about this on Medium.

Solution 9 - Android

Just an alternative to the other answers...

EventBus with MutableShareFlow as it core in a shared object (ex: repo) and an observer describe in here

Looks like things are moving away from LiveData and going in Flow direction.

Worth to have a look.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNominalistaView Question on Stackoverflow
Solution 1 - AndroidLeHaineView Answer on Stackoverflow
Solution 2 - AndroidarsentView Answer on Stackoverflow
Solution 3 - AndroidCrazyView Answer on Stackoverflow
Solution 4 - AndroidAmir LatifiView Answer on Stackoverflow
Solution 5 - AndroidMustafa OzhanView Answer on Stackoverflow
Solution 6 - AndroidVadzimVView Answer on Stackoverflow
Solution 7 - AndroidOscar de la FuenteView Answer on Stackoverflow
Solution 8 - AndroidMahdi NouriView Answer on Stackoverflow
Solution 9 - AndroidHDNView Answer on Stackoverflow