Para corresponder URIs hierárquicos a padrões e extrair argumentos, use
UriDeepLinkMatcher. Ele depende de kotlinx.serialization para desserializar argumentos correspondentes nas classes de chave.
Para criar um UriDeepLinkMatcher, forneça um padrão DeepLinkUri e o
serializador da 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 com suporte
UriDeepLinkMatcher corresponde a URIs com base nos cinco componentes: esquema, autoridade, caminho, consulta e fragmento. As seções a seguir descrevem a sintaxe de padrão com suporte, 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 correspondentes.
Para corresponder a um esquema específico, inclua-o no padrão. Como exceção, um
http esquema em um padrão corresponde a URIs de solicitação http e https, enquanto
https em um padrão corresponde apenas a solicitações https.
| URI de padrão | URI de 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 de 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 de padrão | URI de 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
Há suporte para os seguintes padrões de caminho:
| URI de padrão | URI de 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 consulta
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.
Há suporte para os seguintes padrões de parâmetro de consulta:
| URI de padrão | URI de 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 fragmento
Há suporte para os seguintes tipos de padrão de fragmento:
| URI de padrão | URI de 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
UriDeepLinkMatcher oferece suporte à desserialização de argumentos de URI em tipos primitivos, enumerações, 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 enumerações- Classes
@Serializableaninhadas (cujas propriedades são niveladas em marcadores de posição de URI individuais)
- Primitivos (
- Serialização personalizada com
DeepLinkSerializer: converte entre uma únicaStringe objetos personalizados, tipos externos (comojava.time.LocalDate) ou coleções delimitadas personalizadas.
Serialização padrão
UriDeepLinkMatcher funciona imediatamente para tipos padrão e estruturas niveladas sem exigir implementações de serializador personalizadas.
Primitivos e strings
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 enumeração são correspondidos com diferenciação de maiúsculas e minúsculas aos nomes dos elementos de enumeração:
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 enumeração:
@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 uma NavKey contém uma propriedade cujo tipo é outra classe @Serializable, UriDeepLinkMatcher nivela as propriedades dela para que cada propriedade da classe aninhada seja mapeada diretamente para um parâmetro de URI individual do 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írgula), estenda DeepLinkSerializer<T>.
DeepLinkSerializer<T> é um KSerializer<T> abstrato que converte
entre uma 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
}
Por exemplo, considere as definições de Filter e FilterSerializer que são
usadas nos snippets a seguir:
@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), anote a propriedade com @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"))
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írgula ou delimitados personalizados (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
UriDeepLinkMatcher distingue entre incompatibilidades (retorna null para que outras correspondências possam ser tentadas) 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 cujos 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 correspondente configurados com valores padrão, objetos aninhados e enumerações:
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 correspondentes para vários URIs de solicitação:
| URI de solicitação | Resultado da decodificação | Resultado da correspondência |
|---|---|---|
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 padrão é 12, layer é 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á na enumeração) |
null |
Configurações sem suporte
Se a classe de chave contiver tipos de dados sem suporte, UriDeepLinkMatcher vai gerar uma exceção durante a correspondência em vez de retornar null.
- Mapas e coleções multidimensionais:
UriDeepLinkMatcheroferece suporte apenas a coleções unidimensionais de primitivos, strings, enumerações ou tipos personalizados anotados com umDeepLinkSerializer.Maptipos geram umaIllegalArgumentException, enquanto as coleções aninhadas (comoList<List<String>>) geram umaSerializationException. - 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: classes aninhadas
@Serializablenã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
UriMatchResult instâncias são classificadas usando os seguintes critérios em
ordem:
- Tipo MatchResult:
UriMatchResulttem uma classificação mais alta do que outrosMatchResulttipos. - 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 caractere 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 aquelas que não capturam.
- Contagem total de argumentos: o número total de argumentos (caminho, consulta, fragmento) é o desempate final.
Personalizar UriDeepLinkMatcher
UriDeepLinkMatcher é uma classe open que pode ser criada 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 umDeepLinkRequestrecebido. Substitua isso para inspecionar extras de solicitação ou aplicar pré-condições personalizadas antes da correspondência de URI.matchUri: corresponde aoDeepLinkUriao 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 criação de subclasses de 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) } }