简体   繁体   English

使用 Jetpack Compose 的深层链接导航到可组合项

[英]Navigating to a composable using a deeplink with Jetpack Compose

When a user enters a geo-fence in our app, we show them an offer notification about the area, which when clicked, should direct them to a specific composable screen called SingleNotification .当用户在我们的应用程序中输入地理围栏时,我们会向他们显示有关该区域的报价通知,单击该通知应将他们引导到名为SingleNotification的特定可组合屏幕。 I've followed google's codelab and their documentation but I haven't managed to make the navigation to the specific screen work yet.我已经关注了谷歌的代码实验室和他们的文档,但我还没有设法使导航到特定屏幕的工作。 Right now, clicking on the notification or running the adb shell am start -d “eway://station_offers/date_str/www.test.com/TITLE/CONTENT” -a android.intent.action.VIEW command, simply opens the app.现在,单击通知或运行adb shell am start -d “eway://station_offers/date_str/www.test.com/TITLE/CONTENT” -a android.intent.action.VIEW命令,只需打开应用程序.

The activity is declared as follows in the manifest:该活动在清单中声明如下:

    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:label="@string/app_name"
        android:screenOrientation="portrait">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />

            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
        </intent-filter>

        <intent-filter>
            <action android:name="android.intent.action.VIEW" />

            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />

            <data
                android:host="station_offers"
                android:scheme="eway" />
        </intent-filter>
    </activity>

Our MainNavController class contains the NavHost which in turn contains various NavGraphs.我们的 MainNavController 类包含 NavHost,而 NavHost 又包含各种 NavGraphs。 I've only included the relevant graph below:我只包括了下面的相关图表:

        NavHost(
            navController = navController,
            startDestination = NavigationGraphs.SPLASH_SCREEN.route
        ) {
....
            notificationsNavigation()
....    
    }

The notificationsNavigation graph is defined as follows:通知导航图定义如下:

fun NavGraphBuilder.notificationsNavigation() {
    navigation(
        startDestination = Screens.NOTIFICATION_DETAILS.navRoute,
        route = NavigationGraphs.NOTIFICATIONS.route
    ) {
        composable(
            route = "${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}",
            arguments = listOf(
                navArgument("date") { type = NavType.StringType },
                navArgument("imageUrl") { type = NavType.StringType },
                navArgument("title") { type = NavType.StringType },
                navArgument("content") { type = NavType.StringType }
            ),
            deepLinks = listOf(navDeepLink {
                uriPattern = "eway://${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}"
            })
        ) { backstackEntry ->
            val args = backstackEntry.arguments
            SingleNotification(
                date = args?.getString("date")!!,
                imageUrl = args.getString("imageUrl")!!,
                title = args.getString("title")!!,
                description = args.getString("content")!!
            )
        }
    }
}

The Screes.NOTIFICATION_DETAILS.navRoute corresponds to the value of notification_details . Screes.NOTIFICATION_DETAILS.navRoute对应于notification_details的值。

Inside the geo-fence broadcast receiver, I construct the pending Intent as follows:在地理围栏广播接收器中,我按如下方式构造待处理的 Intent:

                        val deepLinkIntent = Intent(
                            Intent.ACTION_VIEW,
                            "eway://station_offers/${
                                offer.date
                            }/${
                                offer.image
                            }/${offer.title}/${offer.content}".toUri(),
                            context,
                            MainActivity::class.java
                        )
                        val deepLinkPendingIntent: PendingIntent =
                            TaskStackBuilder.create(context!!).run {
                                addNextIntentWithParentStack(deepLinkIntent)
                                getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)!!
                            }
                        showNotification(offer.title, offer.content, deepLinkPendingIntent)

I can't figure out what I'm missing here.我无法弄清楚我在这里缺少什么。

UPDATE : Please see @curioustechizen's answer below for the actual solution instead of this workaround!更新:请参阅下面的@curioustechizen 答案以获取实际解决方案,而不是这种解决方法!

Alright, after a lot of testing and running the solution of Google's relative code lab a bunch of times line by line, I figured out how to make it work.好吧,经过大量测试并逐行运行谷歌相关代码实验室的解决方案,我想出了如何让它工作。 First and foremost, it looks like the host that we define in the AndroidManifest.xml for the <data> tag of the intent filter needs to much the composable destination's route.首先,看起来我们在 AndroidManifest.xml 中为意图过滤器的<data>标签定义的host需要很多可组合目的地的路由。 So in my case, it is defined as:所以在我的例子中,它被定义为:

        <intent-filter>
            <action android:name="android.intent.action.VIEW" />

            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />

            <data
                android:host="notification_details"
                android:scheme="eway" />
        </intent-filter>

Second of all, the deep link's uri pattern should match the composable's route format.其次,深层链接的 uri 模式应该与可组合的路由格式相匹配。 In this case, since the composable's route is defined as route = "${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}" , the correct deep Link uriPattern , would be :在这种情况下,由于可组合的路由定义为route = "${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}" ,因此正确的深层链接uriPattern将是:

deepLinks = listOf(navDeepLink {
                    uriPattern =
                        "eway://${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}"
                })

Furthermore, the composable destination seems to MUST be declared inside the NavHost itself and not inside a NavGraph.此外,可组合目的地似乎必须在NavHost本身内声明,而不是在 NavGraph 内。 Initially as you can see, I thought that the system would be able to find the destination via the nested NavGraph, but it couldn't (threw a relative exception), so I came to the conclusion that it must be done this way (as is done in the code labs).最初如您所见,我认为系统将能够通过嵌套的 NavGraph 找到目的地,但它不能(抛出一个相对异常),所以我得出结论必须以这种方式完成(如在代码实验室中完成)。 Please correct me if I'm wrong!如果我错了,请纠正我!

Lastly, I changed the val uri definition inside my GeofenceBroadcastReceiver accordingly.最后,我相应地更改了 GeofenceBroadcastReceiver 中的val uri定义。 Now it looks like so:现在看起来像这样:

val uri = "eway://${Screens.NOTIFICATION_DETAILS.navRoute}/${
                                    offer.date.replace(
                                        "/",
                                        "@"
                                    )
                                }/${
                                    offer.image.replace(
                                        "/",
                                        "@"
                                    )
                                }/${offer.title}/${offer.content.replace("/", "@")}".toUri()

So to recap, these are the steps that seem to solve this issue as far as my understanding goes:回顾一下,就我的理解而言,这些是似乎可以解决这个问题的步骤:

  1. The deep link's destination composable must be a direct child of the main NavHost深层链接的可组合目标必须是主 NavHost 的直接子级
  2. The AndroidManifest's android:host should match the destination composable's route, and lastly, AndroidManifest 的android:host应该匹配目标可组合的路由,最后,
  3. The deep link's Uri pattern should match the destination composable's route (if you use the format scheme://host/.... you should be fine if you followed number 2)深层链接的 Uri 模式应与目标可组合的路由匹配(如果您使用格式scheme://host/....如果您遵循数字 2,您应该没问题)

It turns out that the limitations described in this answer are not entirely true.事实证明,这个答案中描述的限制并不完全正确。 Specifically,具体来说,

  1. It is possible to deep link from a notification directly into a destination that is inside a nested graph可以从通知直接深度链接到嵌套图中的目标
  2. There is no relation between a destination's route and the deepLink URI.目的地的路由和 deepLink URI 之间没有关系。

Point 2 above was the key to unlock my understanding of how deeplinks work.上面的第 2 点是解开我对深度链接工作原理的理解的关键。 They are just arbitrary URIs and have no relationship to the destination's route at all.它们只是任意的 URI,与目的地的路由完全没有关系。 The rule is that the following 3 items must match up规则是以下3项必须匹配

  1. The URI pattern defined in a composable's navDeepLink DSL在可组合的navDeepLink DSL 中定义的 URI 模式
  2. The URI used to construct a PendingIntent for the notification用于构造通知的PendingIntent的 URI
  3. The scheme and host declared in the intent-filter in the manifest.在清单的intent-filter中声明的schemehost

Here are some code snippets.以下是一些代码片段。 In my case the URIs were static, so you will need to make adjustments in order to address the OP's situation.在我的情况下,URI 是静态的,因此您需要进行调整以解决 OP 的情况。 This example has the following structure此示例具有以下结构

  • LandingScreen ( "landing_screen_route" ) LandingScreen ( "landing_screen_route" )
  • SecondScreen ( "second_screen_route" ) SecondScreen ( "second_screen_route" )
  • A nested graph ( "nested_graph_route" ) with a NestedScreen ( "nested_destination_route" )带有NestedScreen"nested_destination_route" )的嵌套图( "nested_graph_route" nested_graph_route”)

We are going to see how to reach both SecondScreen and NestedScreen from a notification.我们将了解如何从通知中同时SecondScreenNestedScreen

First, defining the NavGraph using the DSL.首先,使用 DSL 定义 NavGraph。 Pay special attention to the navDeepLink entries here.请特别注意此处的navDeepLink条目。

@Composable
fun AppGraph(onNotifyClick: () -> Unit) {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "landing_screen_route"
    ) {
        composable("landing_screen_route") {
            LandingScreen {
                navController.navigate("second_screen_route")
            }
        }
        composable(
            route = "second_screen_route",
            deepLinks = listOf(
                navDeepLink { uriPattern = "myapp://arbitrary_top_level" } // Note that this pattern has no relation to the route itself
            )
        ) {
            SecondScreen {
                navController.navigate("nested_graph_route")
            }
        }
        navigation(
            startDestination = "nested_destination_route",
            route = "nested_graph_route"
        ) {
            composable(
                route = "nested_destination_route",
                deepLinks = listOf(
                    navDeepLink { uriPattern = "myapp://arbitrary_nested" } // Note that this pattern has no relation to the route itself
                )
            ) {
                NestedScreen(onNotifyClick)
            }
        }
    }
}

Next, here's how you would construct the PendingIntent for both these cases:接下来,您将如何为这两种情况构造 PendingIntent:

val notNestedIntent = TaskStackBuilder.create(this).run {
    addNextIntentWithParentStack(
        Intent(
            Intent.ACTION_VIEW,
            "myapp://arbitrary_top_level".toUri() // <-- Notice this
        )
    )
    getPendingIntent(1234, PendingIntent.FLAG_UPDATE_CURRENT)
}

val nestedIntent = TaskStackBuilder.create(this).run {
    addNextIntentWithParentStack(
        Intent(
            Intent.ACTION_VIEW,
            "myapp://arbitrary_nested".toUri() // <-- Notice this
        )
    )
    getPendingIntent(2345, PendingIntent.FLAG_UPDATE_CURRENT)
}

Finally, here are the intent-filter entries in the manifest最后,这是清单中的intent-filter条目

<activity
    android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />

        <!--
            The scheme and host must match both of the below:
            1. The navDeepLink declaration
            2. The URI defined in the PendingIntent
         -->
        <data
            android:scheme="myapp"
            android:host="arbitrary_top_level"
        />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />

        <!--
            The scheme and host must match both of the below:
            1. The navDeepLink declaration
            2. The URI defined in the PendingIntent
         -->
        <data
            android:scheme="myapp"
            android:host="arbitrary_nested"
        />
    </intent-filter>
</activity>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM