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

Ste*_*ail 11 android kotlin android-deep-link android-jetpack-compose

当用户在我们的应用程序中进入地理围栏时,我们会向他们显示有关该区域的优惠通知,单击该通知后,应将他们引导至名为 的特定可组合屏幕SingleNotification。我已经遵循了谷歌的代码实验室及其文档,但我还没有设法使到特定屏幕的导航正常工作。现在,单击通知或运行命令adb shell am start -d \xe2\x80\x9ceway://station_offers/date_str/www.test.com/TITLE/CONTENT\xe2\x80\x9d -a android.intent.action.VIEW,只需打开应用程序即可。

\n

该活动在清单中声明如下:

\n
    <activity\n        android:name=".MainActivity"\n        android:exported="true"\n        android:label="@string/app_name"\n        android:screenOrientation="portrait">\n        <intent-filter>\n            <action android:name="android.intent.action.MAIN" />\n\n            <category android:name="android.intent.category.LAUNCHER" />\n\n            <category android:name="android.intent.category.DEFAULT" />\n            <category android:name="android.intent.category.BROWSABLE" />\n        </intent-filter>\n\n        <intent-filter>\n            <action android:name="android.intent.action.VIEW" />\n\n            <category android:name="android.intent.category.DEFAULT" />\n            <category android:name="android.intent.category.BROWSABLE" />\n\n            <data\n                android:host="station_offers"\n                android:scheme="eway" />\n        </intent-filter>\n    </activity>\n
Run Code Online (Sandbox Code Playgroud)\n

我们的 MainNavController 类包含 NavHost,而 NavHost 又包含各种 NavGraph。我只包含了下面的相关图表:

\n
        NavHost(\n            navController = navController,\n            startDestination = NavigationGraphs.SPLASH_SCREEN.route\n        ) {\n....\n            notificationsNavigation()\n....    \n    }\n
Run Code Online (Sandbox Code Playgroud)\n

通知导航图定义如下:

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

对应Screes.NOTIFICATION_DETAILS.navRoute于 的值notification_details

\n

在地理围栏广播接收器内部,我构造待处理的 Intent,如下所示:

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

我不知道我在这里错过了什么。

\n

cur*_*zen 17

事实证明,这个答案中描述的限制并不完全正确。具体来说,

  1. 可以从通知直接深层链接到嵌套图内的目的地
  2. 目的地的路由和 deepLink URI 之间没有关系。

上面的第 2 点是解锁我对深度链接如何工作的理解的关键。它们只是任意的 URI,与目的地的路由完全没有关系。规则是以下 3 项必须匹配

  1. navDeepLink可组合项的DSL中定义的 URI 模式
  2. PendingIntent用于构造通知的URI
  3. 并在清单中scheme声明。hostintent-filter

这是一些代码片段。在我的例子中,URI 是静态的,因此您需要进行调整以解决 OP 的情况。该示例具有以下结构

  • LandingScreen( "landing_screen_route")
  • SecondScreen( "second_screen_route")
  • 嵌套图 ( "nested_graph_route") 和NestedScreen( "nested_destination_route")

我们将了解如何通过通知到达SecondScreen两者。NestedScreen

首先,使用 DSL 定义 NavGraph。请特别注意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)
            }
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

接下来,以下是针对这两种情况构建 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)
}
Run Code Online (Sandbox Code Playgroud)

最后,这是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>
Run Code Online (Sandbox Code Playgroud)


Ste*_*ail 6

更新:请参阅下面@curioustechizen 的答案以获取实际的解决方案,而不是此解决方法!

好吧,经过大量测试并逐行运行 Google 相关代码实验室的解决方案后,我弄清楚了如何使其工作。首先也是最重要的,host我们在 AndroidManifest.xml 中为<data>意图过滤器的标签定义的内容看起来需要很多可组合目标的路由。所以就我而言,它被定义为:

        <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>
Run Code Online (Sandbox Code Playgroud)

其次,深层链接的 uri 模式应与可组合项的路由格式匹配。在这种情况下,由于可组合项的路由定义为route = "${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}",因此正确的深层链接uriPattern将是:

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

此外,可组合目的地似乎必须在其NavHost自身内部声明,而不是在 NavGraph 内部声明。最初如您所见,我认为系统能够通过嵌套的 NavGraph 找到目的地,但它不能(抛出相对异常),所以我得出的结论是必须这样做(如是在代码实验室中完成的)。如果我错了请纠正我!

最后,我val uri相应地更改了 GeofenceBroadcastReceiver 内的定义。现在看起来像这样:

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

回顾一下,据我所知,这些步骤似乎可以解决这个问题:

  1. 深层链接的目标可组合项必须是主 NavHost 的直接子级
  2. AndroidManifest 应android:host与目标可组合项的路由匹配,最后,
  3. 深层链接的 Uri 模式应与目标可组合项的路线匹配(如果您使用格式,scheme://host/....则遵循数字 2 应该没问题)