Starting a Fragment for results in Android

Google has just added a new ability to FragmentManager which made the FragmentManager be able to act as a central store for fragment results.

All of the source code is available at https://github.com/BenBoonya/fragment-result

As an Android developer, we all should be familiar with the startActivityForResult function. We use this function for starting a new Activity and getting results back to current the Fragment or Activity. However, when the screens in my application are represented by Fragments, and I can't use startActivityForResult for passing results between them.

Until recently, when it comes to the communication between Fragments there is nothing similar to startActivityForResult. Therefore, I had to create shared ViewModel between Fragments so that I can pass data back and forth between them.

Fortunately, Google has just added a new ability to the Fragment family. There are two approaches to pass data back and forth between Fragments; first is by using FragmentManager and second is by using the Navigation component library.

Then let's see how we can use this new ability in action. I will build a simple note list application with two Fragments with the Navigation component library. One fragment for displaying the list of notes and another one for taking note.

NoteListFragment


AddNoteFragment



Let's start by adding important dependencies. By the time this article is written the latest Fragment version is 1.3.0-alpha05.

def nav_version = "2.3.0-beta01"
def fragment_version = "1.3.0-alpha05"

implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"

implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"

Here is the navigation graph for this project.

the navigation graph


All of the source code is available at https://github.com/BenBoonya/fragment-result

Using FragmentManager


The new ability added to FragmentManager made the FragmentManager be able to act as a central store for Fragment results. Although the function is not named startFragmentForResult, it acts similarly to startActivityForResult. With the snippet below, we can pass the data back and forth between Fragments easily. This ability has been added since Fragment 1.3.0-alpha04

For NoteListFragment to get the result back from AddNoteFragment. We have to set a result listener. Let's take a look at the NoteListFragment.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setFragmentResultListener(
        ADD_NOTE
    ) { _, result ->
        result.getString(NOTE)?.let { note ->
            adapter.addItem(note)
        }
    }
}

As you can see, there is an extension function called setFragmentResultListener provided. We get the result back as a bundle.

Moving on to the AddNoteFragment. We need to set result, and it can be done easily as follow. 

private fun navigateBack() {
    setFragmentResult(
        NoteListFragment.ADD_NOTE, bundleOf(
            NoteListFragment.NOTE to binding.editText.text.toString()
        )
    )
    findNavController().popBackStack()
}

The only thing to keep in mind is that you need to set the result on the same FragmentManager using the same requestKey. If we take a closer look into the Fragment extension functions, we can see that both setFragmentResult, setFragmentResultListener are calling on parentFragmentManager in the following snippets. 

inline fun Fragment.setFragmentResultListener(
    requestKey: String,
    crossinline listener: ((resultKey: String, bundle: Bundle) -> Unit)
) {
    parentFragmentManager.setFragmentResultListener(requestKey, this, listener)
}

fun Fragment.setFragmentResult(
    requestKey: String,
    result: Bundle
) = parentFragmentManager.setFragmentResult(requestKey, result)

Another thing that worth mentioning is there can only be a single listener and result for a given key. In our example, if we call setFragmentResult in AddNoteFragment more than once the system will send the most recent result to NoteListFragment before the AddNoteFragment is popped off the back stack.

If the AddNoteFragment happens to be a child Fragment of the NoteListFragment. We just need to set the listener and result on the childFragmentManager instead of parentFragmentManager.


Using the Navigation component library


To return results to previous destinations, the Navigation component library uses the SavedStateHandle which is a key-value map that can be used to store and retrieve data persistently through configuration changes

On NoteListFragment:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>(
        ADD_NOTE
    )?.observe(viewLifecycleOwner, Observer { note ->
        adapter.addItem(note)
    })
}

The savedStateHandle provides a LiveData and we can put the observer on it to get the value.

On AddNoteFragment

findNavController().previousBackStackEntry?.savedStateHandle?.set(
    NoteListFragment.ADD_NOTE,
    binding.editText.text.toString()
)

Here we set the value to the savedStateHandle of the previous back stack entry which belongs to the NoteListFragment

Conclusion

I'm excited to see the stable release of these features. In my opinion, the added ability of the Fragment family made working with the Single activity architecture which has all of the destinations represented by Fragment become much easier and more desirable.

Looking for a new challenge? Join Our Team

Like 6 likes
Ben Kitpitak
Mobile Developer at OOZOU in Bangkok, Thailand
Share:

Join the conversation

This will be shown public
All comments are moderated

Comments

Mario
June 5th, 2021
Hi.
What about two setFragmentResultListener?

Get our stories delivered

From us to your inbox weekly.