Per la corrispondenza degli URI gerarchici con i pattern e l'estrazione degli argomenti, utilizza
UriDeepLinkMatcher. Si basa su kotlinx.serialization per deserializzare gli argomenti corrispondenti nelle classi chiave.
Per creare un UriDeepLinkMatcher, fornisci un pattern DeepLinkUri e il
serializzatore per la chiave corrispondente:
@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")
Per gli URI non gerarchici o gli schemi personalizzati (ad esempio tel:), consulta
Creare matcher di link diretti personalizzati.
Pattern di corrispondenza supportati
UriDeepLinkMatcher mette in corrispondenza gli URI in base ai cinque componenti: schema, autorità, percorso, query e frammento. Le sezioni seguenti descrivono la sintassi dei pattern supportata, i segnaposto degli argomenti e le regole di corrispondenza per ogni componente.
Corrispondenza dello schema
Se nel pattern URI non è presente alcuno schema, vengono messi in corrispondenza sia http sia https.
Per mettere in corrispondenza uno schema specifico, includilo nel pattern. Come eccezione, uno schema
http in un pattern mette in corrispondenza gli URI delle richieste http e https, mentre
https in un pattern mette in corrispondenza solo le richieste https.
| URI del pattern | URI della richiesta | Corrispondenza |
|---|---|---|
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 |
✅ |
Corrispondenza dell'autorità
UriDeepLinkMatcher esegue una corrispondenza esatta senza distinzione tra maiuscole e minuscole sull'autorità URI (host e porta facoltativa). I segnaposto o i caratteri jolly non sono supportati nell'autorità e non vengono estratti argomenti:
| URI del pattern | URI della richiesta | Corrispondenza |
|---|---|---|
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 |
❌ |
Corrispondenza del percorso
Sono supportati i seguenti pattern di percorso:
| URI del pattern | URI della richiesta | Corrispondenza | Argomenti estratti |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | Nessuno |
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: "" (stringa vuota) |
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 |
✅ | Nessuno |
www.example.com/users |
https://www.example.com/users/ |
❌ (la barra finale crea un segmento aggiuntivo) | N/D |
Corrispondenza della query
L'ordine dei parametri di query nell'URI della richiesta non deve corrispondere all'ordine nell'URI del pattern. Inoltre, i parametri presenti nell'URI della richiesta ma non nell'URI del pattern vengono ignorati.
Sono supportati i seguenti pattern di parametri di query:
| URI del pattern | URI della richiesta | Argomenti estratti |
|---|---|---|
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: "" (stringa vuota) |
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" |
Corrispondenza del frammento
Sono supportati i seguenti tipi di pattern di frammenti:
| URI del pattern | URI della richiesta | Argomenti estratti |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
Nessuno |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
Nessuno |
Tipi di dati supportati
UriDeepLinkMatcher supporta la deserializzazione degli argomenti URI in tipi primitivi, enum, raccolte e oggetti personalizzati. La serializzazione rientra in due categorie:
- Serializzazione standard: utilizza
kotlinx.serializationper la deserializzazione in:- Primitivi (
Boolean,Int,Long,Float,Double,Char,Byte,Short) eString - Enum
Set,ListoArraydi primitivi, stringhe o enum- Classi
@Serializablenidificate (le cui proprietà vengono appiattite in singoli segnaposto URI)
- Primitivi (
- Serializzazione personalizzata con
DeepLinkSerializer: converte tra una singolaStringe oggetti personalizzati, tipi esterni (ad esempiojava.time.LocalDate) o raccolte con delimitatori personalizzati.
Serializzazione standard
UriDeepLinkMatcher funziona immediatamente per i tipi standard e le strutture appiattite senza richiedere implementazioni di serializzatori personalizzati.
Primitivi e stringhe
UriDeepLinkMatcher decodifica automaticamente i tipi primitivi (Boolean, Int, Long, Float, Double, Char, Byte, Short) e 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)
Enum
I valori enum vengono messi in corrispondenza con distinzione tra maiuscole e minuscole con i nomi degli elementi enum:
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)
Raccolte di query ripetute
I parametri di query con chiavi ripetute (ad esempio ?id=10&id=20) vengono deserializzati automaticamente in List<T>, Set<T>, o Array<T>, dove T è un tipo primitivo, String, o enum:
@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))
Classi @Serializable nidificate
Quando un NavKey contiene una proprietà il cui tipo è un'altra classe @Serializable, UriDeepLinkMatcher ne appiattisce le proprietà in modo che ogni proprietà della classe nidificata venga mappata direttamente a un singolo parametro URI con lo stesso nome:
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))
Serializzazione personalizzata con DeepLinkSerializer
Per deserializzare oggetti personalizzati (ad esempio Filter(key = "brand", value =
"pixel")), tipi esterni (ad esempio java.time.LocalDate) o stringhe con delimitatori personalizzati (ad esempio valori separati da virgole), estendi DeepLinkSerializer<T>.
DeepLinkSerializer<T> è un KSerializer<T> astratto che converte
tra una String e T:
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
Ad esempio, considera le definizioni Filter e FilterSerializer utilizzate negli snippet seguenti:
@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}" }
Singoli oggetti personalizzati
Per decodificare un oggetto da una singola stringa di parametri URI (ad esempio ?filter=brand:google), annota la proprietà con @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"))
Oggetti personalizzati nei parametri di query ripetuti
Per deserializzare i parametri di query ripetuti in una raccolta di oggetti personalizzati
(List<T>, Set<T>, o Array<T>), implementa DeepLinkSerializer<T> per il
tipo di elemento T e annota l'argomento di tipo della proprietà con
@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")))
Raccolte con delimitatori in singoli parametri
Per analizzare i valori separati da virgole o con delimitatori personalizzati (ad esempio ?ids=1,2,3) in una raccolta, implementa DeepLinkSerializer per l'intero tipo di raccolta e annota la proprietà con @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))
Convalida degli argomenti e risultati della corrispondenza
UriDeepLinkMatcher distingue tra mancate corrispondenze (restituisce null in modo che possano essere tentati altri matcher) e configurazioni non supportate (genera un'eccezione).
Mancate corrispondenze
Si verifica una mancata corrispondenza quando un URI della richiesta in entrata non soddisfa i requisiti di pattern o tipo:
- Parametri obbligatori mancanti: proprietà chiave non nullable senza valori predefiniti i cui parametri URI corrispondenti non sono presenti nell'URI della richiesta.
- Errori di analisi dei tipi: valori degli argomenti estratti che non possono essere analizzati
nel tipo di proprietà previsto (ad esempio,
"abc"per una proprietàInt).
Quando si verifica una mancata corrispondenza, UriDeepLinkMatcher.match restituisce null, consentendo la valutazione dei matcher successivi.
Considera una classe chiave e un matcher configurati con valori predefiniti, oggetti nidificati ed enum:
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>() )
La tabella seguente mostra i risultati della corrispondenza per vari URI delle richieste:
| URI della richiesta | Risultato della decodifica | Risultato della corrispondenza |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Riuscita (tutti i parametri forniti) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
Riuscita (zoom ha come valore predefinito 12, layer ha come valore predefinito STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Riuscita (il parametro di query facoltativo vuoto utilizza il valore predefinito 12) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
Mancata corrispondenza (parametro location obbligatorio mancante) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
Mancata corrispondenza ("close" non è un Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
Mancata corrispondenza ("HYBRID" non è nell'enum) |
null |
Configurazioni non supportate
Se la classe chiave contiene tipi di dati non supportati, UriDeepLinkMatcher genera un'eccezione durante la corrispondenza anziché restituire null.
- Mappe e raccolte multidimensionali:
UriDeepLinkMatchersupporta solo raccolte monodimensionali di primitivi, stringhe, enum o tipi personalizzati annotati con unDeepLinkSerializer. I tipiMapgenerano unIllegalArgumentException, mentre le raccolte nidificate (ad esempioList<List<String>>) generano unSerializationException. - Raccolte di oggetti personalizzati non annotati: le raccolte di tipi personalizzati (ad esempio
List<Filter>) generano unSerializationExceptiona meno che il tipo di elemento non sia annotato con unDeepLinkSerializer. - Classi nidificate non appiattite: le classi
@Serializablenon possono essere mappate a un singolo segnaposto (ad esempio?user={user}) senza unDeepLinkSerializer.
// 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
Confronto di UriMatchResult
Le istanze UriMatchResult vengono classificate utilizzando i seguenti criteri in
ordine:
- Tipo di MatchResult:
UriMatchResultha una classificazione più alta rispetto ad altriMatchResulttipi. - Percorso esatto: le corrispondenze di percorsi letterali hanno una classificazione più alta rispetto alle corrispondenze di segnaposto o caratteri jolly.
- Numero di argomenti del percorso: le corrispondenze con più argomenti del percorso hanno una classificazione più alta.
- Presenza di argomenti: le corrispondenze che acquisiscono argomenti hanno una classificazione più alta rispetto a quelle che non lo fanno.
- Numero totale di argomenti: il numero totale di argomenti (percorso, query, frammento) è l'ultimo criterio di spareggio.
Personalizzare UriDeepLinkMatcher
UriDeepLinkMatcher è una classe open di cui puoi creare una sottoclasse per personalizzare il comportamento di corrispondenza degli URI e di estrazione degli argomenti:
matchRequest: punto di ingresso di corrispondenza di primo livello per unDeepLinkRequestin entrata. Esegui l'override di questo metodo per esaminare gli extra della richiesta o applicare precondizioni personalizzate prima della corrispondenza degli URI.matchUri: Mette in corrispondenzaDeepLinkUricon il pattern configurato. Esegui l'override di questo metodo per intercettare e normalizzare gli URI in entrata (ad esempio, riscrivendo i sottodomini dinamici o i formati di percorso legacy) prima di chiamaresuper.matchUri.matchArguments: deserializza le mappe degli argomenti di percorso, query e frammento estratti in un'istanza di chiave di navigazione utilizzando ilserializerfornito. Esegui l'override di questo metodo per inserire valori dinamici o trasformare gli argomenti prima dell'istanza della chiave.
L'esempio seguente mostra la creazione di una sottoclasse di UriDeepLinkMatcher per normalizzare i prefissi dei percorsi URL legacy prima della corrispondenza:
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) } }