Android Compose Navigation 參數傳遞 & Deep Links
在 Navigation 2.8.0 之後,我們不再需要像拼湊 URL 字串那樣去組合路由參數。取而代之的是使用 Kotlin Serialization 的 Type-Safe Navigation。這讓傳遞參數變得像呼叫函式一樣自然且安全。
基礎參數傳遞
定義 Data Class 作為路由,參數即為建構式參數。
@Serializable
data class Profile(val id: Int, val name: String)
// 導航時直接建立物件
navController.navigate(Profile(id = 123, name = "Alice"))
// 接收參數
composable<Profile> { backStackEntry ->
val profile: Profile = backStackEntry.toRoute()
ProfileScreen(profile.id, profile.name)
}
可選參數 (Optional Arguments)
如果某些參數不是必須的,我們可以提供預設值 (Default Value) 並將型別設為 Nullable。Navigation Compose 會自動將其視為可選參數。
@Serializable
data class Search(
val query: String,
val filter: String? = null // 可選參數,預設為 null
)
// 只傳必填參數
navController.navigate(Search(query = "Android"))
// 傳所有參數
navController.navigate(Search(query = "Android", filter = "Recent"))
傳遞複雜物件 (Complex Objects)
雖然官方建議只傳遞 ID,但有時候傳遞一個小的資料物件 (DTO) 非常方便。在 Type-Safe Navigation 中,這變得容易許多。只要該物件也標註 @Serializable 即可。
@Serializable
data class UserConfig(val darkMode: Boolean, val language: String)
@Serializable
data class Settings(val config: UserConfig) // 巢狀物件
// 導航
navController.navigate(Settings(config = UserConfig(true, "zh-TW")))
// 接收
composable<Settings> { backStackEntry ->
val settings = backStackEntry.toRoute<Settings>()
// 直接使用 settings.config
}
Deep Links (深層連結)
Deep Links 允許使用者從 App 外部(例如網頁連結、通知或其他 App)直接跳轉到 App 內的特定頁面。在 Navigation Compose 中,我們可以透過 deepLinks 參數輕鬆實現此功能。
Type-Safe Deep Links
在使用 Type-Safe Navigation 時,Deep Link 的定義變得非常直觀。我們不再需要手動解析 URL 字串,而是定義 basePath,Navigation 系統會自動將 URI 中的變數對應到我們的 Data Class 參數。
1. 定義路由 (Route)
假設我們有一個產品頁面 ProductScreen,它接受一個路徑參數 id (必填) 和一個查詢參數 referrer (選填)。
@Serializable
data class Product(
val id: String, // 必填 -> 會對應到 Path 參數
val referrer: String? = null // 選填 (有預設值) -> 會對應到 Query 參數
)
2. 設定 Deep Link
在 composable 中加入 deepLinks 設定:
composable<Product>(
deepLinks = listOf(
navDeepLink<Product>(basePath = "https://www.example.com/product")
)
) { backStackEntry ->
val product: Product = backStackEntry.toRoute()
ProductScreen(id = product.id, referrer = product.referrer)
}
這個設定會自動處理以下兩種 URL 格式:
- Path 參數:
https://www.example.com/product/{id}- 例如:
https://www.example.com/product/pixel9 - 解析結果:
Product(id = "pixel9", referrer = null)
- 例如:
- Query 參數:
https://www.example.com/product/{id}?referrer={referrer}- 例如:
https://www.example.com/product/pixel9?referrer=newsletter - 解析結果:
Product(id = "pixel9", referrer = "newsletter")
- 例如:
AndroidManifest 設定
為了讓 Android 系統知道你的 App 可以處理這些連結,必須在 AndroidManifest.xml 中對應的 <activity> (通常是 MainActivity) 加入 <intent-filter>。
<activity ...>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- 定義 Scheme 和 Host -->
<!-- 這樣可以匹配 https://www.example.com/product/... -->
<data android:scheme="https" android:host="www.example.com" android:pathPrefix="/product" />
</intent-filter>
</activity>
測試 Deep Links (ADB 指令)
你不需要真的架設網頁伺服器來測試,可以直接使用 Android Debug Bridge (adb) 工具發送 Intent:
# 測試基本路徑
adb shell am start -W -a android.intent.action.VIEW -d "https://www.example.com/product/pixel9"
# 測試包含 Query Parameter
adb shell am start -W -a android.intent.action.VIEW -d "https://www.example.com/product/pixel9?referrer=email"
其他類型的 Deep Links (Action & MimeType)
除了網址,Navigation Compose 也支援基於 Action 和 MimeType 的跳轉。例如,讓使用者從其他 App 分享圖片時直接開啟你的編輯頁面。
@Serializable
object ImageEditor
composable<ImageEditor>(
deepLinks = listOf(
navDeepLink {
action = Intent.ACTION_SEND
mimeType = "image/*"
}
)
) {
// ... 從 Intent 讀取圖片 URI
}
這同樣需要在 Manifest 中宣告對應的 Intent Filter (Action 為 android.intent.action.SEND, MimeType 為 image/*)。
(Legacy) 舊版字串路由
如果你維護的是舊專案,可能會看到這種寫法。這是 Navigation 2.8.0 之前的標準做法,類似 HTTP URL。
路徑定義:"profile/{userId}?name={name}"
- 必填參數:
{userId} - 可選參數:
?name={name}
composable(
route = "profile/{userId}?name={name}",
arguments = listOf(
navArgument("userId") { type = NavType.StringType },
navArgument("name") {
type = NavType.StringType
nullable = true
defaultValue = null
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
val name = backStackEntry.arguments?.getString("name")
// ...
}
這種寫法容易出錯且型別不安全,建議儘快遷移到 Type-Safe Navigation。
小結
- 總是使用 Type-Safe Navigation (
@Serializable) 定義路由。 - 透過
backStackEntry.toRoute<T>()輕鬆取得型別安全的參數。 - 可選參數透過 Kotlin 的預設值 (
val arg: String? = null) 實現。 - Deep Link 依然強大,只需定義
basePath即可自動對應參數。