Przepis na argumenty identyfikatora URI precyzyjnego linku
Ten przepis pokazuje, jak przeanalizować adres URL precyzyjnego linku z intencji Androida i przekształcić go w klucz nawigacji.
Składniki przepisu
Składa się z 2 działań:
UriWithArgumentsDeepLinkActivitytworzy i wywołuje żądanie precyzyjnego linku.MainActivityanalizuje intencję i przekształca ją w docelowy klucz nawigacji.
Jak to działa
MainActivity obsługuje żądanie w tych krokach:
-
Zadeklaruj
UriDeepLinkMatcherdla każdego wzorca adresu URL, do którego można utworzyć precyzyjny link. Każdy dopasowywacz akceptuje wzorzec identyfikatora URI i KSerializer klucza NavKey, który obsługuje ten precyzyjny link. -
Utwórz
DeepLinkRequestz przychodzącą intencją. -
Dopasuj wszystkie kandydackie
UriDeepLinkMatchersdo żądania i porównaj uzyskaneUriMatchResults, aby znaleźć najlepsze dopasowanie. -
Odczytaj pasujący klucz z
UriMatchResult.keylub użyj klucza domyślnego, jeśli nie ma dopasowania.
Ten przepis koncentruje się na obsłudze intencji i nie obejmuje tych kwestii:
- Tworzenie syntetycznego stosu wstecznego
- Konfiguracja wielomodułowa
- DI
- Zarządzanie TaskStack
- Przycisk „W górę” a przycisk „Wstecz”
Demonstrowane formy precyzyjnego linku
MainActivity ma kilka kluczy stosu wstecznego, które demonstrują różne typy obsługiwanych precyzyjnych linków:
HomeKey– precyzyjny link z dokładnym adresem URL (bez argumentów precyzyjnego linku)UsersKey– precyzyjny link z argumentami ścieżkiSearchKey– precyzyjny link z argumentami zapytania
Rzeczywisty wzorzec adresu URL każdego z nich znajdziesz w MainActivity.deepLinkMatchers.
package com.example.nav3recipes.deeplink.handlerequests.uriarguments import androidx.navigation3.runtime.NavKey import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.STRING_LITERAL_FILTER import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.STRING_LITERAL_HOME import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.STRING_LITERAL_SEARCH import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.STRING_LITERAL_USERS import kotlinx.serialization.Serializable internal interface NavRecipeKey: NavKey { val name: String } @Serializable internal object HomeKey: NavRecipeKey { override val name: String = STRING_LITERAL_HOME } @Serializable internal data class UsersKey( val filter: String, ): NavRecipeKey { override val name: String = STRING_LITERAL_USERS companion object { const val FILTER_KEY = STRING_LITERAL_FILTER const val FILTER_OPTION_RECENTLY_ADDED = "recentlyAdded" const val FILTER_OPTION_ALL = "all" } } @Serializable internal data class SearchKey( val firstName: String? = null, val ageMin: Int? = null, val ageMax: Int? = null, val location: String? = null, ): NavRecipeKey { override val name: String = STRING_LITERAL_SEARCH }
package com.example.nav3recipes.deeplink.handlerequests.uriarguments import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.core.net.toUri import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.deeplink.DeepLinkRequest import androidx.navigation3.runtime.deeplink.UriDeepLinkMatcher import androidx.navigation3.runtime.deeplink.invoke import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.ui.NavDisplay import com.example.nav3recipes.common.deeplink.EntryScreen import com.example.nav3recipes.common.deeplink.FriendsList import com.example.nav3recipes.common.deeplink.LIST_USERS import com.example.nav3recipes.common.deeplink.TextContent import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.URL_HOME_EXACT import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.URL_SEARCH import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.URL_USERS_WITH_FILTER import com.example.nav3recipes.ui.setEdgeToEdgeConfig import kotlinx.serialization.serializer /** * See README.md for how this recipe works. */ class MainActivity : ComponentActivity() { /** STEP 1. Declare supported deep links */ internal val deepLinkMatchers: List<UriDeepLinkMatcher<NavKey>> = listOf( // "https://www.nav3recipes.com/home" UriDeepLinkMatcher(URL_HOME_EXACT.toUri(), serializer<HomeKey>()), // "https://www.nav3recipes.com/users/with/{filter}" UriDeepLinkMatcher(URL_USERS_WITH_FILTER.toUri(), serializer<UsersKey>()), // "https://www.nav3recipes.com/users/search?{firstName}&{age}&{location}" UriDeepLinkMatcher(URL_SEARCH.toUri(), serializer<SearchKey>()), ) override fun onCreate(savedInstanceState: Bundle?) { setEdgeToEdgeConfig() super.onCreate(savedInstanceState) /** STEP 2. Create a [DeepLinkRequest] from the intent */ val request = DeepLinkRequest(intent) /** STEP 3. Match the request to the DeepLinkMatchers*/ // First get all the possible matching UriMatchResult val matches = deepLinkMatchers.mapNotNull { // returns null if no match it.match(request) } // compare all matches to find best match val bestMatch = matches.maxOrNull() /** STEP 4. Get the key from the match or use default key if no match*/ val key = bestMatch?.key ?: HomeKey /** * STEP 5. pass the initial key to backstack */ setContent { val backStack: NavBackStack<NavKey> = rememberNavBackStack(key) NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, entryProvider = entryProvider { entry<HomeKey> { key -> EntryScreen(key.name) { TextContent("<matches exact url>") } } entry<UsersKey> { key -> EntryScreen("${key.name} : ${key.filter}") { TextContent("<matches path argument>") val list = when { key.filter.isEmpty() -> LIST_USERS key.filter == UsersKey.FILTER_OPTION_ALL -> LIST_USERS else -> LIST_USERS.take(5) } FriendsList(list) } } entry<SearchKey> { search -> EntryScreen(search.name) { TextContent("<matches query parameters, if any>") val matchingUsers = LIST_USERS.filter { user -> (search.firstName == null || user.firstName == search.firstName) && (search.location == null || user.location == search.location) && (search.ageMin == null || user.age >= search.ageMin) && (search.ageMax == null || user.age <= search.ageMax) } FriendsList(matchingUsers) } } } ) } } }
package com.example.nav3recipes.deeplink.handlerequests.uriarguments import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateMapOf 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.EMPTY import com.example.nav3recipes.common.deeplink.EntryScreen import com.example.nav3recipes.common.deeplink.FIRST_NAME_JOHN import com.example.nav3recipes.common.deeplink.FIRST_NAME_JULIE import com.example.nav3recipes.common.deeplink.FIRST_NAME_MARY import com.example.nav3recipes.common.deeplink.FIRST_NAME_TOM import com.example.nav3recipes.common.deeplink.LOCATION_BC import com.example.nav3recipes.common.deeplink.LOCATION_BR import com.example.nav3recipes.common.deeplink.LOCATION_CA import com.example.nav3recipes.common.deeplink.LOCATION_US import com.example.nav3recipes.common.deeplink.MenuDropDown import com.example.nav3recipes.common.deeplink.MenuTextInput import com.example.nav3recipes.common.deeplink.PaddedButton import com.example.nav3recipes.common.deeplink.TextContent import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.PATH_BASE import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.PATH_INCLUDE import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.PATH_SEARCH import com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui.STRING_LITERAL_HOME import com.example.nav3recipes.ui.setEdgeToEdgeConfig /** * See README.md for how this recipe works. * * See [MainActivity] for how the requested deeplink is handled. */ class UriWithArgumentsDeepLinkActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { setEdgeToEdgeConfig() super.onCreate(savedInstanceState) setContent { /** * UI for deeplink sandbox */ EntryScreen("Sandbox - Build Your Deeplink") { TextContent("Base url:\n${PATH_BASE}/") var showFilterOptions by remember { mutableStateOf(false) } val selectedPath = remember { mutableStateOf(MENU_OPTIONS_PATH[KEY_PATH]?.first()) } var showQueryOptions by remember { mutableStateOf(false) } var selectedFilter by remember { mutableStateOf("") } val selectedSearchQuery = remember { mutableStateMapOf<String, String>() } // manage path options MenuDropDown( menuOptions = MENU_OPTIONS_PATH, ) { _, selection -> selectedPath.value = selection when (selection) { PATH_SEARCH -> { showQueryOptions = true showFilterOptions = false } PATH_INCLUDE -> { showQueryOptions = false showFilterOptions = true } else -> { showQueryOptions = false showFilterOptions = false } } } // manage path filter options, reset state if menu is closed LaunchedEffect(showFilterOptions) { selectedFilter = if (showFilterOptions) { MENU_OPTIONS_FILTER.values.first().first() } else { "" } } if (showFilterOptions) { MenuDropDown( menuOptions = MENU_OPTIONS_FILTER, ) { _, selected -> selectedFilter = selected } } // manage query options, reset state if menu is closed LaunchedEffect(showQueryOptions) { if (showQueryOptions) { val initEntry = MENU_OPTIONS_SEARCH.entries.first() selectedSearchQuery[initEntry.key] = initEntry.value.first() } else { selectedSearchQuery.clear() } } if (showQueryOptions) { MenuTextInput( menuLabels = MENU_LABELS_SEARCH, ) { label, selected -> selectedSearchQuery[label] = selected } MenuDropDown( menuOptions = MENU_OPTIONS_SEARCH, ) { label, selected -> selectedSearchQuery[label] = selected } } // form final deeplink url val arguments = when (selectedPath.value) { PATH_INCLUDE -> "/${selectedFilter}" PATH_SEARCH -> { buildString { selectedSearchQuery.forEach { entry -> if (entry.value.isNotEmpty()) { val prefix = if (isEmpty()) "?" else "&" append("$prefix${entry.key}=${entry.value}") } } } } else -> "" } val finalUrl = "${PATH_BASE}/${selectedPath.value}$arguments" TextContent("Final url:\n$finalUrl") // deeplink to target PaddedButton("Deeplink Away!", onClick = dropUnlessResumed { val intent = Intent( this@UriWithArgumentsDeepLinkActivity, MainActivity::class.java ) // start activity with the url intent.data = finalUrl.toUri() startActivity(intent) }) } } } } private const val KEY_PATH = "path" private val MENU_OPTIONS_PATH = mapOf( KEY_PATH to listOf( STRING_LITERAL_HOME, PATH_INCLUDE, PATH_SEARCH, ), ) private val MENU_OPTIONS_FILTER = mapOf( UsersKey.FILTER_KEY to listOf(UsersKey.FILTER_OPTION_RECENTLY_ADDED, UsersKey.FILTER_OPTION_ALL), ) private val MENU_OPTIONS_SEARCH = mapOf( SearchKey::firstName.name to listOf( EMPTY, FIRST_NAME_JOHN, FIRST_NAME_TOM, FIRST_NAME_MARY, FIRST_NAME_JULIE ), SearchKey::location.name to listOf(EMPTY, LOCATION_CA, LOCATION_BC, LOCATION_BR, LOCATION_US) ) private val MENU_LABELS_SEARCH = listOf(SearchKey::ageMin.name, SearchKey::ageMax.name)
package com.example.nav3recipes.deeplink.handlerequests.uriarguments.ui import com.example.nav3recipes.deeplink.handlerequests.uriarguments.SearchKey /** * String resources */ internal const val STRING_LITERAL_FILTER = "filter" internal const val STRING_LITERAL_HOME = "home" internal const val STRING_LITERAL_USERS = "users" internal const val STRING_LITERAL_SEARCH = "search" internal const val STRING_LITERAL_INCLUDE = "include" internal const val PATH_BASE = "https://www.nav3recipes.com" internal const val PATH_INCLUDE = "$STRING_LITERAL_USERS/$STRING_LITERAL_INCLUDE" internal const val PATH_SEARCH = "$STRING_LITERAL_USERS/$STRING_LITERAL_SEARCH" internal const val URL_HOME_EXACT = "$PATH_BASE/$STRING_LITERAL_HOME" internal const val URL_USERS_WITH_FILTER = "$PATH_BASE/$PATH_INCLUDE/{$STRING_LITERAL_FILTER}" internal val URL_SEARCH = "$PATH_BASE/$PATH_SEARCH" + "?${SearchKey::ageMin.name}={${SearchKey::ageMin.name}}" + "&${SearchKey::ageMax.name}={${SearchKey::ageMax.name}}" + "&${SearchKey::firstName.name}={${SearchKey::firstName.name}}" + "&${SearchKey::location.name}={${SearchKey::location.name}}"