Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android jetpack navigation component result from dialog

So far I'm successfully able to navigate to dialogs and back using only navigation component. The problem is that, I have to do some stuff in dialog and return result to the fragment where dialog was called from.

One way is to use shared viewmodel. But for that I have to use .of(activity) which leaves my app with a singleton taking up memory, even when I no longer need it.

Another way is to override show(fragmentManager, id) method, get access to fragment manager and from it, access to previous fragment, which could then be set as targetfragment. I've used targetFragment approach before where I would implement a callback interface, so my dialog could notify targetFragment about result. But in navigation component approach it feels hacky and might stop working at one point or another.

Any other ways to do what I want? Maybe there's a way to fix issue on first approach?

like image 773
SMGhost Avatar asked Jun 17 '19 04:06

SMGhost


3 Answers

In Navigation 2.3.0-alpha02 and higher, NavBackStackEntry gives access to a SavedStateHandle. A SavedStateHandle is a key-value map that can be used to store and retrieve data. These values persist through process death, including configuration changes, and remain available through the same object. By using the given SavedStateHandle, you can access and pass data between destinations. This is especially useful as a mechanism to get data back from a destination after it is popped off the stack.

To pass data back to Destination A from Destination B, first set up Destination A to listen for a result on its SavedStateHandle. To do so, retrieve the NavBackStackEntry by using the getCurrentBackStackEntry() API and then observe the LiveData provided by SavedStateHandle.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val navController = findNavController();
// We use a String here, but any type that can be put in a Bundle is supported
navController.currentBackStackEntry?.savedStateHandle?.getLiveData("key")?.observe(
    viewLifecycleOwner) { result ->
    // Do something with the result.
}

}

In Destination B, you must set the result on the SavedStateHandle of Destination A by using the getPreviousBackStackEntry() API.

navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
like image 50
Hamed safari Avatar answered Nov 14 '22 17:11

Hamed safari


Thanks to @NataTse and also the official docs, i came up with the extensions so that hopefully less boilerplate code to write:

fun <T>Fragment.setNavigationResult(key: String, value: T) {
    findNavController().previousBackStackEntry?.savedStateHandle?.set(
        key,
        value
    )
}

fun <T>Fragment.getNavigationResult(@IdRes id: Int, key: String, onResult: (result: T) -> Unit) {
    val navBackStackEntry = findNavController().getBackStackEntry(id)

    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_RESUME
            && navBackStackEntry.savedStateHandle.contains(key)
        ) {
            val result = navBackStackEntry.savedStateHandle.get<T>(key)
            result?.let(onResult)
            navBackStackEntry.savedStateHandle.remove<T>(key)
        }
    }
    navBackStackEntry.lifecycle.addObserver(observer)

    viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            navBackStackEntry.lifecycle.removeObserver(observer)
        }
    })
}
like image 35
dumbfingers Avatar answered Nov 14 '22 17:11

dumbfingers


When you use Navigation Component with dialogs, this part of code looks not so good (for me it returned nothing)

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

You need to try way from official docs and it help me a lot

This part is working for me:

 val navBackStackEntry = navController.getBackStackEntry(R.id.target_fragment_id)

    // Create observer and add it to the NavBackStackEntry's lifecycle
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_RESUME
            && navBackStackEntry.savedStateHandle.contains("key")
        ) {
            val result =
                navBackStackEntry.savedStateHandle.get<Boolean>("key")
            // Do something with the result

        }
    }
    navBackStackEntry.lifecycle.addObserver(observer)

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            navBackStackEntry.lifecycle.removeObserver(observer)
        }
    })

And in your dialog:

navController.previousBackStackEntry?.savedStateHandle?.set(
            "key",
            true
        )
like image 32
NataTse Avatar answered Nov 14 '22 16:11

NataTse