Para comparar URIs hierárquicos com padrões e extrair argumentos, use
UriDeepLinkMatcher. Ele depende de kotlinx.serialization para desserializar
argumentos correspondentes nas suas classes de chave.
Para criar um UriDeepLinkMatcher, forneça um padrão DeepLinkUri e o
serializador para a chave correspondente:
@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")
Para URIs não hierárquicos ou esquemas personalizados (como tel:), consulte
Criar correspondências de link direto personalizadas.
Padrões de correspondência aceitos
UriDeepLinkMatcher faz a correspondência de URIs com base nos cinco componentes: esquema,
autoridade, caminho, consulta e fragmento. As seções a seguir descrevem a sintaxe de padrão compatível, os marcadores de posição de argumento e as regras de correspondência para cada componente.
Correspondência de esquema
Se nenhum esquema estiver presente no padrão de URI, http e https serão correspondidos.
Para corresponder a um esquema específico, inclua-o no padrão. Como exceção, um esquema http em um padrão corresponde aos URIs de solicitação http e https, enquanto https em um padrão corresponde apenas às solicitações https.
| URI do padrão | URI da solicitação | Correspondência |
|---|---|---|
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 |
✅ |
Correspondência de autoridade
UriDeepLinkMatcher realiza uma correspondência exata que não diferencia maiúsculas de minúsculas na autoridade do URI (host e porta opcional). Marcadores de posição ou caracteres curinga não são aceitos
na autoridade, e nenhum argumento é extraído:
| URI do padrão | URI da solicitação | Correspondência |
|---|---|---|
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 |
❌ |
Correspondência de caminho
Os seguintes padrões de caminho são compatíveis:
| URI do padrão | URI da solicitação | Correspondência | Argumentos extraídos |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | Nenhum |
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: "" (string vazia) |
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 |
✅ | Nenhum |
www.example.com/users |
https://www.example.com/users/ |
❌ (A barra final cria um segmento extra) | N/A |
Correspondência de consultas
A ordem dos parâmetros de consulta no URI de solicitação não precisa corresponder à ordem no URI de padrão. Além disso, os parâmetros presentes no URI de solicitação, mas não no URI de padrão, são ignorados.
Os seguintes padrões de parâmetro de consulta são compatíveis:
| URI do padrão | URI da solicitação | Argumentos extraídos |
|---|---|---|
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: "" (string vazia) |
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" |
Correspondência de fragmentos
Os seguintes tipos de padrão de fragmento são compatíveis:
| URI do padrão | URI da solicitação | Argumentos extraídos |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
Nenhum |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
Nenhum |
Tipos de dados com suporte
O UriDeepLinkMatcher oferece suporte à desserialização de argumentos de URI em tipos
primitivos, tipos enumerados, coleções e objetos personalizados. A serialização se divide em duas
categorias:
- Serialização padrão: usa
kotlinx.serializationpara desserializar em:- Primitivos (
Boolean,Int,Long,Float,Double,Char,Byte,Short) eString - Enumerações
Set,ListouArrayde primitivos, strings ou tipos enumerados- Classes
@Serializableaninhadas (com propriedades niveladas em marcadores de posição de URI individuais)
- Primitivos (
- Serialização personalizada com
DeepLinkSerializer: converte entre um únicoStringe objetos personalizados, tipos externos (comojava.time.LocalDate) ou coleções personalizadas.
Serialização padrão
O UriDeepLinkMatcher funciona de forma integrada para tipos padrão e estruturas
niveladas sem exigir implementações de serializador personalizadas.
Primitivos e strings
O UriDeepLinkMatcher decodifica automaticamente tipos primitivos (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)
Enumerações
Os valores de tipo enumerado são comparados com os nomes dos elementos de tipo enumerado, diferenciando maiúsculas de minúsculas:
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)
Coleções de consultas repetidas
Os parâmetros de consulta com chaves repetidas (como ?id=10&id=20) são desserializados automaticamente
em List<T>, Set<T> ou Array<T>, em que T é um tipo
primitivo, String ou tipo enumerado:
@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))
Classes @Serializable aninhadas
Quando um NavKey contém uma propriedade cujo tipo é outra classe @Serializable,
o UriDeepLinkMatcher nivela as propriedades para que cada propriedade da classe
aninhada seja mapeada diretamente para um parâmetro de URI individual com o mesmo 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))
Serialização personalizada com DeepLinkSerializer
Para desserializar objetos personalizados (como Filter(key = "brand", value =
"pixel")), tipos externos (como java.time.LocalDate) ou strings delimitadas personalizadas (como valores separados por vírgulas), estenda DeepLinkSerializer<T>.
DeepLinkSerializer<T> é um KSerializer<T> abstrato que faz a conversão
entre um String e um T:
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
Por exemplo, considere as definições de Filter e FilterSerializer usadas nos seguintes snippets:
@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}" }
Objetos personalizados únicos
Para decodificar um objeto de uma única string de parâmetro de URI (como
?filter=brand:google), use a anotação @Serializable(with = ...) na propriedade:
@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"))
Objetos personalizados em parâmetros de consulta repetidos
Para desserializar parâmetros de consulta repetidos em uma coleção de objetos personalizados
(List<T>, Set<T> ou Array<T>), implemente DeepLinkSerializer<T> para o
tipo de elemento T e anote o argumento de tipo da propriedade com
@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")))
Coleções delimitadas em parâmetros únicos
Para analisar valores separados por vírgulas ou delimitados de forma personalizada (como ?ids=1,2,3) em
uma coleção, implemente DeepLinkSerializer para o tipo de coleção inteiro
e anote a propriedade com @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))
Validação de argumentos e resultados correspondentes
O UriDeepLinkMatcher distingue entre incompatibilidades (retorna null para que
outros correspondentes possam ser tentados) e configurações sem suporte (gera uma
exceção).
Incompatibilidades
Uma incompatibilidade ocorre quando um URI de solicitação recebido não atende aos requisitos de padrão ou tipo:
- Parâmetros obrigatórios ausentes: propriedades de chave não anuláveis sem valores padrão em que os parâmetros de URI correspondentes estão ausentes do URI de solicitação.
- Falhas de análise de tipo: valores de argumento extraídos que não podem ser analisados
no tipo de propriedade esperado (por exemplo,
"abc"para uma propriedadeInt).
Quando ocorre uma incompatibilidade, UriDeepLinkMatcher.match retorna null, permitindo que os correspondentes subsequentes sejam avaliados.
Considere uma classe de chave e um matcher configurados com valores padrão, objetos aninhados e tipos enumerados:
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>() )
A tabela a seguir demonstra os resultados de correspondência para vários URIs de solicitação:
| URI da solicitação | Resultado da decodificação | Resultado da partida |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Sucesso (todos os parâmetros fornecidos) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
Sucesso (zoom assume como padrão 12, layer assume como padrão STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Sucesso (o parâmetro de consulta opcional vazio usa o padrão 12) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
Incompatibilidade (parâmetro location obrigatório ausente) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
Incompatibilidade ("close" não é um Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
Incompatibilidade ("HYBRID" não está no tipo enumerado) |
null |
Configurações sem suporte
Se a classe de chave tiver tipos de dados incompatíveis, o UriDeepLinkMatcher vai gerar
uma exceção durante a correspondência em vez de retornar null.
- Mapas e coleções multidimensionais: o
UriDeepLinkMatchersó oferece suporte a coleções unidimensionais de primitivos, strings, tipos enumerados ou tipos personalizados anotados com umDeepLinkSerializer. Os tiposMapgeram umIllegalArgumentException, enquanto as coleções aninhadas (comoList<List<String>>) geram umSerializationException. - Coleções de objetos personalizados não anotados: coleções de tipos personalizados (como
List<Filter>) geram umaSerializationException, a menos que o tipo de elemento seja anotado com umDeepLinkSerializer. - Classes aninhadas não niveladas: as classes
@Serializableaninhadas não podem ser mapeadas para um único marcador de posição (como?user={user}) sem umDeepLinkSerializer.
// 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
Comparação de UriMatchResult
As instâncias UriMatchResult são classificadas usando os seguintes critérios em
ordem:
- Tipo MatchResult:
UriMatchResulttem uma classificação mais alta do que outros tipos deMatchResult. - Caminho exato: as correspondências de caminho literal têm uma classificação mais alta do que as correspondências de marcador de posição ou de caracteres curinga.
- Contagem de argumentos de caminho: as correspondências com mais argumentos de caminho têm uma classificação mais alta.
- Presença de argumentos: as correspondências que capturam argumentos têm uma classificação mais alta do que as que não capturam.
- Contagem total de argumentos: o número total de argumentos (caminho, consulta, fragmento) é o critério de desempate final.
Personalizar UriDeepLinkMatcher
UriDeepLinkMatcher é uma classe open que pode ser usada como subclasse para personalizar a correspondência de URI
e o comportamento de extração de argumentos:
matchRequest: ponto de entrada de correspondência de nível superior para umDeepLinkRequestde entrada. Substitua isso para inspecionar extras de solicitação ou aplicar condições prévias personalizadas antes da correspondência de URI.matchUri: faz a correspondência deDeepLinkUricom o padrão configurado. Substitua isso para interceptar e normalizar URIs recebidos (por exemplo, reescrever subdomínios dinâmicos ou formatos de caminho legados) antes de chamarsuper.matchUri.matchArguments: desserializa os mapas de argumentos de caminho, consulta e fragmento extraídos em uma instância de chave de navegação usando oserializerfornecido. Substitua isso para injetar valores dinâmicos ou transformar argumentos antes da instanciação da chave.
O exemplo a seguir demonstra a subclasse UriDeepLinkMatcher para normalizar prefixos de caminho do URL legados antes da correspondência:
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) } }