ディープリンクの合成バックスタックのレシピ

このレシピでは、合成 backStack と Task スタックを管理することで、ディープリンクのコンテキストでナビゲーションの原則を適用する方法を示します。

レシピの構成

このレシピでは、「アプリ A」から「アプリ B」へディープリンクする実際のシナリオをシミュレートします。

「アプリ A」は、モジュール syntheticbackstack によってシミュレートされます。このモジュールには、ディープリンク インテントを作成して、既存の Task または新しい Task でトリガーできる SyntheticBackStackDeepLinkActivity が含まれています。

「アプリ B」は、ディープリンク先の SyntheticBackStackAppActivity を含むモジュール syntheticbackstackapp によってシミュレートされます。このモジュールでは、合成 backStack を構築する方法と、戻るボタンと上ボタンの両方をサポートするために Task スタックを適切に管理する方法について説明します。

使用方法

メインの appsyntheticbackstackapp の両方がエミュレータまたは接続されたデバイスにインストールされていることを確認します。インストールされている syntheticbackstackapp"www.nav3deeplink.com" リンクをサポートしていることを確認します。

レシピのランディング ページでフィルタを選択し、ボタンをクリックしてディープリンクします。syntheticbackstackapp のアクティビティが表示されます。

仕組み

このレシピは、こちらにまとめられているディープリンクのガイドラインに沿っています。

Existing Task の動作を確認するには:

  1. 現在のタスクを使用してディープリンクを開く
  2. デバイスで上にスワイプして、最近使ったアプリをすべて表示する
  3. 新しいアクティビティが Nav3Recipes アプリ内で開いていることを確認する
  4. [戻る] ボタンをクリックして元のアクティビティに戻る
  5. 手順 1 を繰り返す
  6. [上へ] ボタンをクリックして親画面に移動する
  7. デバイスで上にスワイプして、最近使ったアプリをすべて表示する
  8. 新しいアクティビティが Nav3SyntheticBackStack アプリ内で開いていることを確認する

New Task の動作を確認するには:

  1. 新しいタスクを使用してディープリンクを開く
  2. デバイスで上にスワイプして、最近使ったアプリをすべて表示する
  3. 新しいアクティビティが Nav3SyntheticBackStack アプリ内で開いていることを確認する
  4. [上へ] ボタンまたは [戻る] ボタンをクリックして親画面に移動する

コア実装

navigateUp と合成バックスタックの構築のためのコア ヘルパー関数は こちらにあります

関連情報

ディープリンクの原則と Navigation 3 での適用方法について詳しくは、ディープリンク ガイドをご覧ください。

package com.example.nav3recipes.deeplink.handlerequests.syntheticbackstack

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.core.net.toUri
import androidx.lifecycle.compose.dropUnlessResumed
import com.example.nav3recipes.common.deeplink.EntryScreen
import com.example.nav3recipes.common.deeplink.LIST_FIRST_NAMES
import com.example.nav3recipes.common.deeplink.LIST_LOCATIONS
import com.example.nav3recipes.common.deeplink.MenuDropDown
import com.example.nav3recipes.common.deeplink.PaddedButton
import com.example.nav3recipes.common.deeplink.TextContent
import com.example.nav3recipes.ui.setEdgeToEdgeConfig

internal const val ADVANCED_PATH_BASE = "https://www.nav3deeplink.com"

/**
 * The recipe entry point that allows users to create a deep link and make a request with it.
 *
 * **HOW THIS RECIPE WORKS** This recipe simulates a real-world scenario where "App A" deep links
 * into "App B".
 *
 * "App A" is simulated by this current module `syntheticbackstack`, which
 * contains the [SyntheticBackStackDeepLinkActivity] that allows you to create a deeplink intent and
 * trigger that in either the existing Task, or in a new Task.
 *
 * "App B" is simulated by the module `syntheticbackstackapp`, which contains
 * the SyntheticBackStackAppActivity that you deeplink into. That module shows you how to build a synthetic backStack
 * and how to manage the Task stack properly in order to support both Back and Up buttons.
 *
 * See the [README](README.md) file of current module for more info on advanced deep linking.
 */
class SyntheticBackStackDeepLinkActivity: ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setEdgeToEdgeConfig()
        super.onCreate(savedInstanceState)

        setContent {
            EntryScreen("Sandbox - Build Your Deeplink Intent") {
                val initFirstName = MENU_OPTIONS_FIRST_NAME.values.first().first()
                val initLocation = MENU_OPTIONS_LOCATION.values.last().first()
                val initTaskStack = MENU_OPTIONS_TASK_STACK.values.first().first()
                var firstName by remember { mutableStateOf(initFirstName) }
                var location by remember { mutableStateOf(initLocation) }
                var taskStack by remember { mutableStateOf(initTaskStack) }

                // select first name
                MenuDropDown(
                    menuOptions = MENU_OPTIONS_FIRST_NAME,
                ) { _, selected ->
                    firstName = selected
                }

                // select first name
                MenuDropDown(
                    menuOptions = MENU_OPTIONS_LOCATION,
                ) { _, selected ->
                    location = selected
                }

                // select current task stack or build new task stack
                MenuDropDown(
                    menuOptions = MENU_OPTIONS_TASK_STACK,
                ) { _, selected ->
                    taskStack = selected
                }

                // build final deeplink URL and Intent
                val finalUrl = "${ADVANCED_PATH_BASE}/user/$firstName/$location"

                // display Intent info
                val flagString = if (taskStack == TAG_NEW_TASK) {
                    "Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK"
                } else "<none>"
                val intentString = """
                    | Final Intent:
                    | data = "$finalUrl"
                    | action = Intent.ACTION_VIEW
                    | flags = $flagString
                """.trimMargin()

                TextContent(intentString)

                // deeplink to target
                PaddedButton("Deeplink Away!", onClick = dropUnlessResumed {
                    val intent = Intent().apply {
                        data = finalUrl.toUri()
                        action = Intent.ACTION_VIEW
                        if (taskStack == TAG_NEW_TASK) {
                            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                        }
                    }

                    startActivity(intent)
                })
            }
        }
    }
}

private const val TAG_FIRST_NAME = "firstName"
private const val TAG_LOCATION = "location"
private const val TAG_TASK_STACK = "Task stack"
private const val TAG_CURRENT_TASK = "Use Current Task Stack"
private const val TAG_NEW_TASK = "Start New Task Stack"

private val MENU_OPTIONS_FIRST_NAME = mapOf(
    TAG_FIRST_NAME to LIST_FIRST_NAMES
)

private val MENU_OPTIONS_LOCATION = mapOf(
    TAG_LOCATION to LIST_LOCATIONS
)

private val MENU_OPTIONS_TASK_STACK = mapOf(
    TAG_TASK_STACK to listOf(TAG_CURRENT_TASK, TAG_NEW_TASK),
)