Deeplinking to another module with parameters
Asked Answered
R

3

10

I'm trying to navigate using a deep-link for cross-module user navigation, and I need to pass some parameters. Since it's in another module, I don't have access to the id, so all of the navigate(@IdRes int resId, ...) methods are off the table.

What's the best way to navigate a deep link with a Uri and a Bundle of key-value-pairs using Android Jetpack's Navigation component?

navigation.xml (:app module)

<?xml version="1.0" encoding="utf-8"?>
<navigation 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/navGraph"
    app:startDestination="@id/feature_one">

    <include app:graph="@navigation/feature_one" />
    <include app:graph="@navigation/feature_two />
</navigation>

feature_one.xml (:one module)

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/feature_one"
    app:startDestination="@id/oneFragment">
    <fragment
        android:id="@+id/oneFragment"
        android:name="my.app.OneFragment"
        android:label="OneFragment" />
</navigation>

feature_two.xml (:two module)

<?xml version="1.0" encoding="utf-8"?>
<navigation 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/feature_two"
    app:startDestination="@id/twoFragment">

    <fragment
        android:id="@+id/twoFragment"
        android:name="my.app.TwoFragment"
        android:label="TwoFragment">
        <deepLink app:uri="myapp://my.app/?myId={myId}" />

        <argument android:name="myId" app:argType="long" />

    </fragment>
</navigation>

OneFragment.kt (:one module)

    val bundle = bundleOf("myId" to 123L)

    val request = NavDeepLinkRequest.Builder
         .fromUri("myapp://my.app?myId={myId}")
         .build()

    // No place to bundle the args

    findNavController().navigate(
        request,
        navOptions
    )

TwoFragment.kt (:two module)

    private val args: TwoFragmentArgs by navArgs()

    ...
    
        val myId: Long = args.myId // never set, so how?
Russo answered 18/9, 2021 at 16:2 Comment(1)
did you find any way to pass the bundle tyler?Military
G
5

tldr: double check make sure you are encoding everything correctly while creating the request, especially if you are using parameters with more than one word, whitespaces or special character, you will need to use the Uri.Builder methods that already encode things for you or encode them yourself beforehand

Your xmls are defined correctly, the problem is how you are building the URI.

I was doing almost the same as you but using

    val myId = 123L
    val uri =  Uri.Builder().path("myapp://my.app/$myId")
        .build()
    val request = NavDeepLinkRequest.Builder
        .fromUri(uri)
        .build()
    findNavController().navigate(request)

And the deep link couldn't match this URI and it gives this Exception java.lang.IllegalArgumentException: Navigation destination that matches request NavDeepLinkRequest{...

I was able to solve it by changing the way the URI was built

Building it like this

val myId = 123L
val uri =  Uri.Builder().scheme("myapp")
    .authority("my.app")
    .path("/$myId")
    .build()
val request = NavDeepLinkRequest.Builder
    .fromUri(uri)
    .build()
findNavController().navigate(request)

or like this (as in @SUR4IDE answer)

val myId = 123L
val correctyEncodedId = URLEncoder.encode(myId, "utf-8")
val request = NavDeepLinkRequest.Builder
   .fromUri(Uri.parse("myapp://my.app/$correctyEncodedId"))
   .build()
    findNavController().navigate(request)

Both approaches avoid a problem where the path String was being encoded somewhat different, especially if the argument has whitespaces or special characters that are differently encoded in a URI, and was not matching the deep link defined within feature_two.xml in my scenario. The URI string was being encoded as "myapp%3A//my.app/123" and this : encoding difference was making the match not work and so it was like I wasn't using the defined deep link.

When the match works, the Navigation classes will match the arguments from your URI to the ones defined in the .xml (they have to be named exactly the same, as you did) and will create a Bundle with them as args and pass it to the Fragment or Activity it will navigate to.

Gredel answered 28/7, 2022 at 21:10 Comment(0)
T
2

This took a while, but it works like a charm. The parameter I've attached (URL encoded) to the URI appears in my Fragment as an argument.


                val request = NavDeepLinkRequest.Builder
                    .fromUri("litpro.mx.lpanalytics://sessionsSyncFilter/${URLEncoder.encode(it, "utf-8")}".toUri())
                    .build()
                findNavController().navigate(request)
        <deepLink app:uri="litpro.mx.lpanalytics://sessionsSyncFilter/{metadataFileSyncFilter}"
            app:action="android.intent.action.VIEW"/>
        arguments?.let { args ->
            args.getString("metadataFileSyncFilter")?.let { sessionMetaDataFile ->
        }
Toplevel answered 9/8, 2022 at 23:58 Comment(0)
S
0

Lets say we want to navigate from fragment A in first_nav_child which is in module A to fragment B in secend_nav_child in module B and fragment B is exepting an argument too.

In second_nav_child graph, lets say we have our fragment B added like this with a deeplink:

<fragment android:id="@+id/fragment_b"
  android:name="com.example.module_b_presentaion.Fragment_b"
  android:label="FragmentB"
  tools:layout="@layout/b_fragment">
  <argument
  android:name="id"
  app:argType="string" />
  <deepLink app:uri="@string/deep_link" />
  </fragment>

As you can see here instead of giving a link, a string resouce is used. Because these two modules have no way to tell each other about the link provided for the deeplink, if you run your code witout using a string resouse to provide the uri, the moment you want to navigate to fragment B your app will crash because fragment A has no knowledge of the string that contains the link.

To solve this problem, Assuming that there is a core module in your app offering shared prefrences, data store ... and also string resources. It is expected that core module has been implemented in all of your modules' gradle as a dependency. We need to provide out deeplink uri strin here in string resources. for example :

<string name="admin_deep_link" 
translatable="false">
myProject://com.Example/module_b/module_b_presentation/{id} 
</string>

Now to navigate to fragment B from fragment A, use the deeplink like this :

val deeplink = NavDeepLinkRequest.Builder.fromUri(
                            Uri.parse(
                                getString(com.example.core.R.string
                                    .deep_link).replace(
                                    "{id}",
                                    "123456"
                                )
                            )
                        ).build()
                    findNavController().navigate(deeplink)
Slingshot answered 6/3, 2023 at 5:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.