Using TargetFragment with Jetpack Navigation Component
By: Luke Needham
Tags: Android App Development, Android Apps, Android, AndroidDev, Fragments

The highly anticipated spiritual successor to Listeners in DialogFragments

TLDR: Checkout this Gist

Welcome back after our last journey of discovery — we now know to use interfaces with targetFragment in order to create callbacks in DialogFragments! This allows us to write callbacks which are preserved across recreation, type-safe, and highly semantic. Hooray!

It probably won’t surprise you to learn that exactly the same pattern can be used for all Fragments — they don’t necessarily need to be Dialogs.

However, this poses an issue when using the Jetpack Navigation Component. Previously, we simply did:

val dialog = DummyDialog()

dialog.setTargetFragment(this, DIALOG_DUMMY)

With the Navigation Component, we don’t manually instantiate our Fragments — so there’s no way for us to set the target Fragment… or is there?

FragmentFactory

This is where we meet the hero of the story: FragmentFactory . This is the class responsible for instantiating Fragments in the FragmentManager . And guess what: Navigation Component is built on top of FragmentManager !

Here’s how this looks:

abstract class AutoTargetFragmentFactory : FragmentFactory() { abstract fun getCurrentFragment(): Fragment? override fun instantiate(

classLoader: ClassLoader,

className: String

): Fragment {

val fragment = super.instantiate(classLoader, className)

val currentFragment = getCurrentFragment()

fragment.setTargetFragment(currentFragment, REQUEST_CODE)

return fragment

} companion object {

const val REQUEST_CODE = 0

}

}

What’s going on here?

FragmentFactory.instantiate is called whenever FragmentManager wants to instantiate a new Fragment. We don’t want to interfere with any of this logic, so we delegate to the super class. Once the super class has done its work, we simply set the target fragment and pass the result along. Easy!

But how do we know which Fragment to pass as the Target Fragment? That’s where our new abstract getCurrentFragment function comes in. Stay tuned!

getCurrentFragment

How can we determine the current Fragment? Well, it’s simply the one on the top of the Fragment Manager stack.

class ChildManagerFragmentFactory(

private val fragmentManager: FragmentManager

) : AutoTargetFragmentFactory() {

override fun getCurrentFragment() = fragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.firstOrNull()

}

If we pass our Activity top-level FragmentManager as the fragmentManager parameter, primaryNavigationFragment will be our NavHostFragment . NavHostFragment contains its own FragmentManager, which is why we request its childFragmentManager , and then simply retrieve the first Fragment in its fragments list.

We’re nearly done!

Wiring it up

We have our ChildManagerFragmentFactory ready and raring to go. Now we just need to tell our FragmentManager.

fun FragmentManager.autoTarget() {

fragmentFactory = ChildManagerFragmentFactory(this)

}

We call FragmentManager.setFragmentFactory , to tell the FragmentManager that Fragment instantiation should be delegated to our custom class. And then we wrap this into a handy extension function, which allows us to simply do:

class MainActivity : AppCompatActivity(R.layout.activity_main) {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

supportFragmentManager.autoTarget()

}

}

And that’s it - you simply need to add this one line to get it all working! You don’t need to change your navigation actions or directions in any way. Continue using findNavController().navigate exactly as before!

Usage

You can now access the correct targetFragment in all your (Dialog)Fragments, exactly as you’re used to. But, as one little bonus, I’ve written a small little extension for maximum semantics.

inline fun <reified TargetFragmentType> Fragment.withCallback(work: TargetFragmentType.() -> Unit) {

val typedTarget = targetFragment as? TargetFragmentType

if (typedTarget == null) {

Log.e(

"AutoTargetFragment",

"Target Fragment is not of expected type ${TargetFragmentType::class.qualifiedName}"

)

return

}

typedTarget.work()

}

And simply use with:

withCallback< MyCallbackType > { myCallback () }

Conclusion

Here’s the full Gist. Feel free to copy-paste into your own projects!

https://gist.github.com/LukeNeedham/83f0bdaa8d56d03d11f727967eb327f2