Aby dopasować hierarchiczne identyfikatory URI do wzorców i wyodrębnić argumenty, użyj
UriDeepLinkMatcher. Do deserializacji dopasowanych argumentów do klas kluczy używa ona biblioteki kotlinx.serialization.
Aby utworzyć klasę UriDeepLinkMatcher, podaj wzorzec DeepLinkUri i
serializator dla odpowiedniego klucza:
@Serializable data class UserProfileKey(val id: String) : NavKey val userProfilePattern = DeepLinkUri("www.example.com/users/{id}") val userProfileMatcher = UriDeepLinkMatcher(userProfilePattern, serializer<UserProfileKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/users/123") val matchResult = userProfileMatcher.match(request) val key = matchResult?.key // UserProfileKey(id = "123")
W przypadku niehierarchicznych identyfikatorów URI lub schematów niestandardowych (np. tel:) zapoznaj się z artykułem
Tworzenie niestandardowych dopasowań precyzyjnych linków.
Obsługiwane wzorce dopasowania
Klasa UriDeepLinkMatcher dopasowuje identyfikatory URI na podstawie 5 komponentów: schematu, autorytetu, ścieżki, zapytania i fragmentu. W sekcjach poniżej znajdziesz opis obsługiwanej składni wzorca, symboli zastępczych argumentów i reguł dopasowania dla każdego komponentu.
Dopasowanie schematu
Jeśli we wzorcu identyfikatora URI nie ma schematu, dopasowywane są zarówno schematy http, jak i https.
Aby dopasować konkretny schemat, uwzględnij go we wzorcu. Wyjątkiem jest schemat
http we wzorcu, który dopasowuje zarówno identyfikatory URI żądań http, jak i https, natomiast
https we wzorcu dopasowuje tylko żądania https.
| Wzorzec URI | Identyfikator URI żądania | Dopasowanie |
|---|---|---|
www.example.com |
https://www.example.com |
✅ |
www.example.com |
http://www.example.com |
✅ |
http://www.example.com |
http://www.example.com |
✅ |
http://www.example.com |
https://www.example.com |
✅ |
https://www.example.com |
http://www.example.com |
❌ |
myapp://www.example.com |
myapp://www.example.com |
✅ |
Dopasowanie autorytetu
Klasa UriDeepLinkMatcher wykonuje dokładne dopasowanie autorytetu identyfikatora URI (hosta i opcjonalnego portu) bez uwzględniania wielkości liter. Symbole zastępcze ani symbole wieloznaczne nie są obsługiwane w autorytecie, a argumenty nie są wyodrębniane:
| Wzorzec URI | Identyfikator URI żądania | Dopasowanie |
|---|---|---|
example.com |
https://example.com |
✅ |
example.com |
https://EXAMPLE.COM |
✅ |
example.com |
https://sub.example.com |
❌ |
example.com |
https://www.example.com |
❌ |
example.com |
https://example.com:8080 |
❌ |
example.com:8080 |
https://example.com:8080 |
✅ |
example.com:8080 |
https://example.com |
❌ |
Dopasowanie ścieżki
Obsługiwane są te wzorce ścieżek:
| Wzorzec URI | Identyfikator URI żądania | Dopasowanie | Wyodrębnione argumenty |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | Brak |
www.example.com/users/{id} |
https://www.example.com/users/123 |
✅ | id: "123" |
www.example.com/users/{first}-{last} |
https://www.example.com/users/john-doe |
✅ | first: "john", last: "doe" |
www.example.com/users/{id}/profile |
https://www.example.com/users//profile |
✅ | id: "" (pusty ciąg znaków) |
www.example.com/users/user_{id} |
https://www.example.com/users/user_123 |
✅ | id: "123" |
www.example.com/users/{userId}/posts/{postId} |
https://www.example.com/users/123/posts/456 |
✅ | userId: "123", postId: "456" |
www.example.com/users/.* |
https://www.example.com/users/john-doe |
✅ | Brak |
www.example.com/users |
https://www.example.com/users/ |
❌ (ukośnik na końcu tworzy dodatkowy segment) | Nie dotyczy |
Dopasowanie zapytania
Kolejność parametrów zapytania w identyfikatorze URI żądania nie musi być zgodna z kolejnością w identyfikatorze URI wzorca. Dodatkowo parametry występujące w identyfikatorze URI żądania, ale nie w identyfikatorze URI wzorca, są ignorowane.
Obsługiwane są te wzorce parametrów zapytania:
| Wzorzec URI | Identyfikator URI żądania | Wyodrębnione argumenty |
|---|---|---|
www.example.com/users?name={name} |
https://www.example.com/users?name=john |
name: "john" |
www.example.com/users?name={name} |
https://www.example.com/users?name= |
name: "" (pusty ciąg znaków) |
www.example.com/users?{rawQuery} |
https://www.example.com/users?anything&else |
rawQuery: ["anything", "else"] |
www.example.com/users?type=user_{id} |
https://www.example.com/users?type=user_123 |
id: "123" |
www.example.com/users?name={first}_{last} |
https://www.example.com/users?name=john_doe |
first: "john", last: "doe" |
www.example.com/users?list={list} |
https://www.example.com/users?list=10&list=20 |
list: ["10", "20"] |
www.example.com/users?name={name}&{other} |
https://www.example.com/users?name=john&tab=info |
name: "john", other: ["tab=info"] |
www.example.com/users?type=user_.* |
https://www.example.com/users?type=user_admin |
type: "admin" |
Dopasowanie fragmentu
Obsługiwane są te typy wzorców fragmentów:
| Wzorzec URI | Identyfikator URI żądania | Wyodrębnione argumenty |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
Brak |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
Brak |
Obsługiwane typy danych
Klasa UriDeepLinkMatcher obsługuje deserializację argumentów identyfikatora URI do typów prostych, wyliczeń, kolekcji i obiektów niestandardowych. Serializacja dzieli się na 2 kategorie:
- Serializacja standardowa: używa
kotlinx.serializationdo deserializacji do:- typów prostych (
Boolean,Int,Long,Float,Double,Char,Byte,Short) i typuString - wyliczeń
Set,ListlubArraytypów prostych, ciągów znaków lub wyliczeń- zagnieżdżonych klas
@Serializable(których właściwości są spłaszczane do poszczególnych symboli zastępczych identyfikatora URI)
- typów prostych (
- Serializacja niestandardowa za pomocą klasy
DeepLinkSerializer: konwertuje między pojedynczym typemStringa obiektami niestandardowymi, typami zewnętrznymi (np.java.time.LocalDate) lub kolekcjami zdefiniowanymi przez użytkownika.
Serializacja standardowa
Klasa UriDeepLinkMatcher działa od razu w przypadku typów standardowych i spłaszczonych struktur bez konieczności implementowania serializatorów niestandardowych.
Typy proste i ciągi znaków
Klasa UriDeepLinkMatcher automatycznie dekoduje typy proste (Boolean, Int, Long, Float, Double, Char, Byte, Short) i typ String:
@Serializable data class UserProfileKey(val id: Int) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/users/{id}"), serializer<UserProfileKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/users/123") val key = matcher.match(request)?.key // UserProfileKey(id = 123)
Wyliczenia
Wartości wyliczeń są dopasowywane z uwzględnieniem wielkości liter do nazw elementów wyliczenia:
enum class SortOrder { RELEVANCE, DATE, POPULARITY } @Serializable data class ProductsKey(val sort: SortOrder) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/products?sort={sort}"), serializer<ProductsKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/products?sort=DATE") val key = matcher.match(request)?.key // ProductsKey(sort = SortOrder.DATE)
Powtarzające się kolekcje zapytań
Parametry zapytania z powtarzającymi się kluczami (np. ?id=10&id=20) są automatycznie
deserializowane do List<T>, Set<T>, lub Array<T>, gdzie T jest typem prostym
typem String lub wyliczeniem:
@Serializable data class FilteredItemsKey(val ids: List<Int>) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/items?id={ids}"), serializer<FilteredItemsKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/items?id=10&id=20") val key = matcher.match(request)?.key // FilteredItemsKey(ids = listOf(10, 20))
Zagnieżdżone klasy @Serializable
Gdy klasa NavKey zawiera właściwość, której typem jest inna klasa @Serializable, klasa UriDeepLinkMatcher spłaszcza jej właściwości, tak aby każda właściwość klasy zagnieżdżonej była bezpośrednio mapowana na poszczególny parametr identyfikatora URI o tej samej nazwie:
enum class SortOrder { RELEVANCE, DATE, POPULARITY } @Serializable data class SearchFilters( val category: String, val sortBy: SortOrder = SortOrder.RELEVANCE ) @Serializable data class SearchKey( val query: String, val page: Int = 1, // Flattened into {category} and {sortBy} val filters: SearchFilters ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/search?q={query}&page={page}&category={category}&sortBy={sortBy}"), serializer<SearchKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/search?q=kotlin&category=books&sortBy=DATE") val key = matcher.match(request)?.key // SearchKey(query = "kotlin", page = 1, filters = SearchFilters(category = "books", sortBy = SortOrder.DATE))
Serializacja niestandardowa za pomocą klasy DeepLinkSerializer
Aby deserializować obiekty niestandardowe (np. Filter(key = "brand", value =
"pixel")), typy zewnętrzne (np. java.time.LocalDate) lub ciągi znaków zdefiniowane przez użytkownika (np. wartości rozdzielone przecinkami), rozszerz klasę DeepLinkSerializer<T>.
DeepLinkSerializer<T> jest abstrakcyjną klasą KSerializer<T>, która konwertuje
między typem String a typem T:
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
Na przykład rozważ definicje klas Filter i FilterSerializer, które są
używane w tych fragmentach kodu:
@Serializable data class Filter(val key: String, val value: String) object FilterSerializer : DeepLinkSerializer<Filter>() { override val serialName: String = "com.example.Filter" override fun deserialize(value: String): Filter { val parts = value.split(":", limit = 2) if (parts.size < 2) { throw SerializationException("Invalid filter: $value. Expected key:value.") } return Filter(key = parts[0], value = parts[1]) } override fun serialize(value: Filter): String = "${value.key}:${value.value}" }
Pojedyncze obiekty niestandardowe
Aby zdekodować obiekt z pojedynczego ciągu znaków parametru identyfikatora URI (np. ?filter=brand:google), dodaj do właściwości adnotację @Serializable(with = ...):
@Serializable data class CatalogKey( @Serializable(with = FilterSerializer::class) val filter: Filter ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/catalog?filter={filter}"), serializer<CatalogKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/catalog?filter=brand:google") val key = matcher.match(request)?.key // CatalogKey(filter = Filter("brand", "google"))
Obiekty niestandardowe w powtarzających się parametrach zapytania
Aby deserializować powtarzające się parametry zapytania do kolekcji obiektów niestandardowych
(List<T>, Set<T>, lub Array<T>), zaimplementuj DeepLinkSerializer<T> dla
typu elementu T i dodaj do argumentu typu właściwości adnotację
@Serializable(with = ...):
@Serializable data class SearchResultsKey( val query: String, val filters: List<@Serializable(with = FilterSerializer::class) Filter> = emptyList() ) : NavKey val searchResultsPattern = DeepLinkUri("www.example.com/search?q={query}&filter={filters}") val searchResultsMatcher = UriDeepLinkMatcher(searchResultsPattern, serializer<SearchResultsKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/search?q=phone&filter=brand:google&filter=color:hazel") val matchResult = searchResultsMatcher.match(request) val key = matchResult?.key // SearchResultsKey(query = "phone", filters = listOf(Filter("brand", "google"), Filter("color", "hazel")))
Kolekcje rozdzielone w pojedynczych parametrach
Aby przeanalizować wartości rozdzielone przecinkami lub zdefiniowane przez użytkownika (np. ?ids=1,2,3) do kolekcji, zaimplementuj klasę DeepLinkSerializer dla całego typu kolekcji i dodaj do właściwości adnotację @Serializable(with = ...):
object IntListCsvSerializer : DeepLinkSerializer<List<Int>>() { override val serialName: String = "com.example.IntListCsv" override fun deserialize(value: String): List<Int> { if (value.isEmpty()) return emptyList() return value.split(",").map { it.trim().toInt() } } override fun serialize(value: List<Int>): String = value.joinToString(",") } @Serializable data class ItemListKey( @Serializable(with = IntListCsvSerializer::class) val ids: List<Int> ) : NavKey val itemListPattern = DeepLinkUri("www.example.com/items/{ids}") val itemListMatcher = UriDeepLinkMatcher(itemListPattern, serializer<ItemListKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/items/10,20,30") val key = itemListMatcher.match(request)?.key // ItemListKey(ids = listOf(10, 20, 30))
Weryfikacja argumentów i wyniki dopasowania
Klasa UriDeepLinkMatcher rozróżnia niezgodności (zwraca wartość null, aby można było wypróbować inne dopasowania) i nieobsługiwane konfiguracje (zgłasza wyjątek).
Niezgodności
Niezgodność występuje, gdy przychodzący identyfikator URI żądania nie spełnia wymagań wzorca lub typu:
- Brak wymaganych parametrów: właściwości klucza, które nie dopuszczają wartości null i nie mają wartości domyślnych , a odpowiadające im parametry identyfikatora URI nie występują w identyfikatorze URI żądania.
- Błędy analizowania typu: wyodrębnione wartości argumentów, których nie można przeanalizować
do oczekiwanego typu właściwości (np.
"abc"dla właściwościInt).
Gdy wystąpi niezgodność, metoda UriDeepLinkMatcher.match zwraca wartość null, co umożliwia ocenę kolejnych dopasowań.
Rozważ klasę klucza i dopasowanie skonfigurowane z wartościami domyślnymi, obiektami zagnieżdżonymi i wyliczeniami:
enum class MapLayer { STANDARD, SATELLITE, TERRAIN } @Serializable data class LayerOptions( val style: String, val layer: MapLayer = MapLayer.STANDARD ) @Serializable data class MapKey( val location: String, val zoom: Int = 12, val options: LayerOptions ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/map/{location}?zoom={zoom}&style={style}&layer={layer}"), serializer<MapKey>() )
W tabeli poniżej przedstawiono wyniki dopasowania dla różnych identyfikatorów URI żądań:
| Identyfikator URI żądania | Wynik dekodowania | Wynik dopasowania |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Sukces (podano wszystkie parametry) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
Sukces (zoom ma domyślną wartość 12, a layer – STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Sukces (pusty opcjonalny parametr zapytania używa domyślnej wartości 12) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
Niezgodność (brak wymaganego parametru location) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
Niezgodność ("close" nie jest typem Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
Niezgodność ("HYBRID" nie występuje w wyliczeniu) |
null |
Nieobsługiwane konfiguracje
Jeśli klasa klucza zawiera nieobsługiwane typy danych, klasa UriDeepLinkMatcher zgłasza wyjątek podczas dopasowywania zamiast zwracać wartość null.
- Mapy i kolekcje wielowymiarowe:
UriDeepLinkMatcherobsługuje tylko kolekcje jednowymiarowe typów prostych, ciągów znaków, wyliczeń lub typów niestandardowych z adnotacjąDeepLinkSerializer. TypyMapzgłaszają wyjątekIllegalArgumentException, a kolekcje zagnieżdżone (np.List<List<String>>) zgłaszają wyjątekSerializationException. - Kolekcje obiektów niestandardowych bez adnotacji: kolekcje typów niestandardowych (np.
takich jak
List<Filter>) zgłaszają wyjątekSerializationException, chyba że typ elementu ma adnotacjęDeepLinkSerializer. - Niespłaszczone klasy zagnieżdżone: klasy zagnieżdżone
@Serializablenie mogą być mapowane na pojedynczy symbol zastępczy (np.?user={user}) bez klasyDeepLinkSerializer.
// Throws IllegalArgumentException: Map decoding is not supported. @Serializable data class InvalidKey(val tags: Map<String, String>) : NavKey // Throws SerializationException: Only collections of primitives are supported. @Serializable data class InvalidKey(val filters: List<Filter>) : NavKey
Porównanie klasy UriMatchResult
UriMatchResult instancje są klasyfikowane według tych kryteriów w
podanej kolejności:
- Typ MatchResult:
UriMatchResultma wyższą rangę niż inneMatchResulttypy. - Dokładna ścieżka: dopasowania literalne ścieżki mają wyższą rangę niż dopasowania symboli zastępczych lub symboli wieloznacznych.
- Liczba argumentów ścieżki: dopasowania z większą liczbą argumentów ścieżki mają wyższą rangę.
- Obecność argumentów: dopasowania, które przechwytują argumenty, mają wyższą rangę niż te, które tego nie robią.
- Łączna liczba argumentów: łączna liczba argumentów (ścieżki, zapytania, fragmentu) jest ostatecznym kryterium rozstrzygającym.
Dostosowywanie klasy UriDeepLinkMatcher
Klasa UriDeepLinkMatcher jest klasą open, której podklasę możesz utworzyć, aby dostosować dopasowywanie identyfikatorów URI i wyodrębnianie argumentów:
matchRequest: najwyższy poziom punktu wejścia dopasowania dla przychodzącegoDeepLinkRequest. Zastąp tę metodę, aby sprawdzić dodatkowe informacje o żądaniu lub zastosować niestandardowe warunki wstępne przed dopasowaniem identyfikatora URI.matchUri: dopasowuje klasęDeepLinkUrido skonfigurowanego wzorca. Zastąp tę metodę, aby przechwytywać i normalizować przychodzące identyfikatory URI (np. przepisywać dynamiczne subdomeny lub formaty ścieżek starszego typu) przed wywołaniem metodysuper.matchUri.matchArguments: deserializuje wyodrębnione mapy argumentów ścieżki, zapytania i fragmentu do instancji klucza nawigacji za pomocą podanegoserializer. Zastąp tę metodę, aby wstrzykiwać wartości dynamiczne lub przekształcić argumenty przed utworzeniem instancji klucza.
Ten przykład pokazuje, jak utworzyć podklasę klasy UriDeepLinkMatcher, aby normalizować prefiksy ścieżek adresów URL starszego typu przed dopasowaniem:
class LegacyPrefixUriDeepLinkMatcher<T : Any>( uriPattern: DeepLinkUri, serializer: KSerializer<T> ) : UriDeepLinkMatcher<T>(uriPattern, serializer) { override fun matchUri(uri: DeepLinkUri): UriMatchResult<T>? { val path = uri.path val normalizedUri = if (path != null && path.startsWith("/legacy/")) { DeepLinkUri(uri.toString().replaceFirst("/legacy", "")) } else { uri } return super.matchUri(normalizedUri) } }