Hi, how wrong is it to rely on `NavDestination.cre...
# compose
m
Hi, how wrong is it to rely on
NavDestination.createRoute
to use
Parcelable
with Navigation Component when using the new Route System (String based) for Fragments/Composables? I understand that it is an internal API, and I’m not supposed to use it, so I’m coming here to find a better option before making a final decision (see the thread for more details). I appreciate any help you can provide.
First of all, I understand it is an internal API (annotated), and I do expect it to change in the future at any point. I am also aware the Jetpack Team does not recommend using a
Parcelable
with the Route System, but in my current scenario, I have 3 options: 1. To ignore their opinion and find a way to use
Parcelable
(cheaper option); 2. Give up on using Navigation Component, and write a custom navigation system; 3. Rewrite all my navigation logic in the entire application (most expensive option). The overall context is that I have a huge multi-activity application. There,
Parcelable
is used to define a
NavigationKey
to navigate an
Activity
in the multi-module structure in a safe way. My goal is to support Single Activity; and not to rewrite all routes, argument decoding, etc... the Single Activity is already a big work, having to care about that too is simple too much. Therefore, we came up with a hack solution to reuse what we already have:
Copy code
// Method exposing `createRoute`.
fun NavController.navigate(
   route: String,
   args: Bundle?,
   navOptions: NavOptions? = null,
   navigatorExtras: Navigator.Extras? = null
) = navigate(NavDestination.createRoute(route).hashCode(), args, navOptions, navigatorExtras)

// Key contract - could be better, just for example.
abstract class NavigationKey : Parcelable {
   
   val route: String get() = requireNotNull(this::class.qualifiedName)
   
   companion object {
     const val TAG: String = "NAVIGATION_KEY"
   }
}

// A destination key
@Parcelize
data class HomeKey(
   val id: String,
   // other attrs.
) : NavigationKey()

// How to use it.
fun example() {
   val navController: NavController // ...

   // Navigating:
   val key = HomeKey(id = "123")
   navController.navigate(route = key.route, args = bundleOf(NavigationKey.TAG to HomeKey(id="123")))
   
   // Reading arguments (from a Fragment):
   val key = requireArguments().getParcelable<HomeKey>(NavigationKey.TAG)
}
EDIT: Ah, and here goes
createRoute
implementation:
Copy code
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
        public fun createRoute(route: String?): String =
            if (route != null) "<android-app://androidx.navigation/$route>" else ""
That works. But I feel really bad about doing it. If anyone has other ideas, I would be glad to hear. Thank you in advance!
i
I'm confused on what you're doing now. If you are in a multi-activity, not using Navigation world, you already need to rewrite all of your navigation logic, correct?
m
Kind of, but not completely. For example, today we have something like the following:
Copy code
// create a key that will be used to navigate.
@Parcelize
interface HomeKey(
    val id: String,
    // ... other attrs.
) : NavigationKey

// navigates
fun onClickButton() {
    val key = HomeKey(id = "123")
    val intent = intentFactory.createIntent(key) // Maps a key to an intent factory, which will know what activity is targetting, and add the key to the args bundle.
    startActivity(intent)
}

// read the key from within an Activity/Fragment
val arg by lazy {
    requireArguments().getKey<HomeKey>() // in practice, `getParcelable<HomeKey>("NAVIGATION_KEY")`
}
Now, with the “hack solution” using
Parcelable
from above, we managed to get the following result for navigation (without touch the key or how to read a key). The following code "works" in our codebase using the hack and allow us to reuse a lot of code:
Copy code
// navigates
fun onClickButton() {
    val key = HomeKey(id = "123")
    findNavController().navigate(key)
}
It is true we would still need to change many things (well, we need to move from
Activity
to
Fragment
, and hopefully to Compose at some point) - but be able to use route +
Parcelable
allow us to keep an abstraction very similar to what we have today to reduce the quantity of changes we need to do at once. However, we rely on
createRoute
which is far from ideal as it is an internal API (the only way we found to use
Parcelable
with string routes). By the way, thanks for replying me (again) and helping with Navigation Component (and sorry for the long message). 🙏
i
Can you explain how your key system works in your multi-module approach? Does that mean you have one or more common modules that are just filled with Key classes that become a compile time dependency between otherwise separate modules?
m
Yes, exactly. We have one
navigation-keys
module that contain most keys. Alternatively, a feature (e.g.,
search
) can define an
-api
module (e.g.,
search-api
) that have their public API, which may include keys. And the
IntentFactory
is a simple Dagger’s Multi-Binding that receives, in summary,
Map<NavigationKey, (NavigationKey -> Intent)>
and wire everything together.
Note that our problem is that we built a system using
Parcelable
(years ago) and now,
route: String
does not allow us to use
Parcelable
- which makes it really hard for us to reuse what we have.