Ever since Android Architecture components came to light, Android development has become much simpler and more efficient. On the other hand, concepts like single source of truth and single activity brought a massive transformation.
With such high-quality development ideas, developers tend to move from traditional activities to effective fragments. This puts many developers in a tricky position(myself included in some cases) regarding data communication between fragments.
Passing data from one fragment to another without any references (context, interfaces, shared view model, or application-level live data) seems almost impossible. But if you look a bit deeper, you’ll find a solution: targetFragment
.
targetFragment
provides a way to communicate with the fragments in the back stack. There is no need for any reference to the destination in the source. In simple terms, targetFragment
is a way to implement onActivityResult
’s functionality in fragments.
Let’s call showOptionsDialog()
in the parent fragment, which shows an OptionsDialogFragment
with a list of options. The only new thing we do here is to set the target fragment with a request code:
private fun showOptionsDialog() {
val optionsDialogInstance = OptionsDialogFragment()
optionsDialogInstance.setTargetFragment(this, 1)
optionsDialogInstance.show(childFragmentManager, optionsDialogInstance.tag)
}
Then we need to implement onActivityResult
in the fragment where we need to receive the data. After that, the only thing left is to pass the data from the source fragment. Have a look:
fun onExit(selectionValue: String){
val intent = Intent()
intent.putExtra("selection",selectionValue)
intent.putExtra(TYPE,type)
targetFragment?.onActivityResult(targetRequestCode, Activity.RESULT_OK, intent)
}
Well, this is nice. We can pass data to a fragment without any reference to it. But the problem is targetFragment
’s functionality only works when both the source and destination fragments are on the same fragment manager. If a fragment is inflated on the childfragmentmanager
, then targetFragment
won’t work.
After nearly a decade, the Android team started focusing on this issue. With the release of [Fragment 1.3.0-alpha04](https://developer.android.com/jetpack/androidx/releases/fragment#1.3.0-alpha04)
, each [FragmentManager](https://developer.android.com/reference/androidx/fragment/app/FragmentManager)
now implements [FragmentResultOwner](https://developer.android.com/reference/androidx/fragment/app/FragmentResultOwner)
. This means that a FragmentManager
can act as a central store for fragment results.
This change allows individual fragments to communicate with each other by setting fragment results and listening for those results without requiring fragments to have direct references of each other. Unlike targetFragment
, it works across fragment managers.
First, let’s see how to pass data at the FragmentManager
level and then between parent and child FragmentManagers
. To pass data to the destination fragment from the source fragment, we have to add a listener with a specific key. Only the bundle that was posted by any other fragments with this key will be invoked here. Have a look:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("requestKey") { key, bundle ->
val result = bundle.getString("name")
// Do something with the result...
}
}
Listener in source fragment where we need to retrieve the data
Now, it’s time to pass data from the source fragment, as shown below:
tvSave.setOnClickListener {
setResult("requestKey", bundleOf("name" to updatedValue))
}
Passing data from source fragment using setResult
This is pretty much straightforward and works fine when both fragments are on the same FragmentManager
. setResult
always delivers the latest data to the destination if you posted multiple times. If the listener is not set at the time of invoking setResult
, it’ll store the data and delivery when a listener is assigned. Most importantly, remember that you should only declare a listener with a specific key.
What about communication between child and parent fragments? Well, we do have a solution for that too. To explain it at a high level, child fragments pass data to childfragmentmanager
, then it’ll pass the data to parent fragment. The implementation part is similar above. The only difference is that we need to add the listener to the child fragment manager, as shown below:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// We set the listener on the child fragmentManager
childFragmentManager.setResultListener("uniquerequestKey") { key, bundle ->
val result = bundle.getString("name")
// Do something with the result..
}
}
Setting a listener in the child fragment
That’s all. Passing data is the same as above. We need to invoke setResult
with a unique key and data with the bundle.
#programming #mobile #kotlin #java #android