Untuk mencocokkan URI hierarkis dengan pola dan mengekstrak argumen, gunakan
UriDeepLinkMatcher. Library ini mengandalkan kotlinx.serialization untuk melakukan deserialisasi
argumen yang cocok ke dalam class kunci Anda.
Untuk membuat UriDeepLinkMatcher, berikan pola DeepLinkUri dan
serializer untuk kunci yang sesuai:
@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")
Untuk URI non-hierarkis atau skema kustom (seperti tel:), lihat
Membuat pencocok deep link kustom.
Pola pencocokan yang didukung
UriDeepLinkMatcher mencocokkan URI berdasarkan lima komponennya: skema,
otoritas, jalur, kueri, dan fragmen. Bagian berikut menjelaskan sintaksis pola yang didukung, placeholder argumen, dan aturan pencocokan untuk setiap komponen.
Pencocokan skema
Jika tidak ada skema dalam pola URI, http dan https akan dicocokkan.
Untuk mencocokkan skema tertentu, sertakan skema tersebut dalam pola. Sebagai pengecualian, skema
http dalam pola cocok dengan URI permintaan http dan https, sedangkan
https dalam pola hanya cocok dengan permintaan https.
| URI Pola | URI Permintaan | Cocok |
|---|---|---|
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 |
✅ |
Pencocokan otoritas
UriDeepLinkMatcher melakukan pencocokan persis yang tidak peka huruf besar/kecil pada otoritas URI (host dan port opsional). Placeholder atau karakter pengganti tidak didukung
dalam otoritas, dan tidak ada argumen yang diekstrak:
| URI Pola | URI Permintaan | Cocok |
|---|---|---|
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 |
❌ |
Pencocokan jalur
Pola jalur berikut didukung:
| URI Pola | URI Permintaan | Cocok | Argumen yang Diekstrak |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | Tidak ada |
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 kosong) |
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 |
✅ | Tidak ada |
www.example.com/users |
https://www.example.com/users/ |
❌ (Garis miring di akhir membuat segmen tambahan) | T/A |
Pencocokan kueri
Urutan parameter kueri dalam URI permintaan tidak harus sama dengan urutan dalam URI pola. Selain itu, parameter yang ada di URI permintaan, tetapi tidak ada di URI pola akan diabaikan.
Pola parameter kueri berikut didukung:
| URI Pola | URI Permintaan | Argumen yang Diekstrak |
|---|---|---|
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 kosong) |
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" |
Pencocokan fragmen
Jenis pola fragmen berikut didukung:
| URI Pola | URI Permintaan | Argumen yang Diekstrak |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
Tidak ada |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
Tidak ada |
Jenis data yang didukung
UriDeepLinkMatcher mendukung deserialisasi argumen URI menjadi jenis primitif, enum, koleksi, dan objek kustom. Serialisasi terbagi menjadi dua kategori:
- Serialisasi standar: Menggunakan
kotlinx.serializationuntuk melakukan deserialisasi menjadi:- Primitif (
Boolean,Int,Long,Float,Double,Char,Byte,Short) danString - Enum
Set,List, atauArrayprimitif, string, atau enum- Class
@Serializablebertingkat (yang propertinya diratakan menjadi placeholder URI individual)
- Primitif (
- Serialisasi kustom dengan
DeepLinkSerializer: Mengonversi antara satuStringdan objek kustom, jenis eksternal (sepertijava.time.LocalDate), atau koleksi yang dibatasi kustom.
Serialisasi standar
UriDeepLinkMatcher dapat langsung digunakan untuk jenis standar dan struktur yang di-flatten tanpa memerlukan implementasi serializer kustom.
Primitif dan string
UriDeepLinkMatcher otomatis mendekode jenis primitif (Boolean, Int,
Long, Float, Double, Char, Byte, Short) dan 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
Nilai enum dicocokkan dengan nama elemen enum dengan memperhatikan huruf besar/kecil:
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)
Koleksi kueri berulang
Parameter kueri dengan kunci berulang (seperti ?id=10&id=20) otomatis
dideserialisasi menjadi List<T>, Set<T>, atau Array<T> dengan T adalah jenis
primitif, String, atau 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))
Class @Serializable bertingkat
Jika NavKey berisi properti yang jenisnya adalah class @Serializable lain,
UriDeepLinkMatcher akan meratakan propertinya sehingga setiap properti class bertingkat
dipetakan langsung ke parameter URI individual dengan nama yang sama:
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))
Serialisasi kustom dengan DeepLinkSerializer
Untuk melakukan deserialisasi objek kustom (seperti Filter(key = "brand", value =
"pixel")), jenis eksternal (seperti java.time.LocalDate), atau string yang dibatasi kustom (seperti CSV), perluas DeepLinkSerializer<T>.
DeepLinkSerializer<T> adalah KSerializer<T> abstrak yang mengonversi
antara String dan T:
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
Misalnya, pertimbangkan definisi Filter dan FilterSerializer yang digunakan dalam cuplikan berikut:
@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}" }
Objek kustom tunggal
Untuk mendekode objek dari satu string parameter URI (seperti
?filter=brand:google), anotasikan properti dengan @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"))
Objek kustom dalam parameter kueri berulang
Untuk melakukan deserialisasi parameter kueri berulang ke dalam kumpulan objek kustom
(List<T>, Set<T>, atau Array<T>), terapkan DeepLinkSerializer<T> untuk
jenis elemen T dan anotasikan argumen jenis properti dengan
@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")))
Koleksi yang dibatasi dalam parameter tunggal
Untuk mengurai nilai yang dipisahkan koma atau dibatasi kustom (seperti ?ids=1,2,3) menjadi
koleksi, terapkan DeepLinkSerializer untuk seluruh jenis koleksi
dan anotasikan properti dengan @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))
Validasi argumen dan hasil pencocokan
UriDeepLinkMatcher membedakan antara ketidakcocokan (menampilkan null sehingga
pencocok lain dapat dicoba) dan konfigurasi yang tidak didukung (memunculkan
pengecualian).
Ketidakcocokan
Ketidakcocokan terjadi jika URI permintaan masuk tidak memenuhi persyaratan pola atau jenis:
- Parameter wajib tidak ada: Properti kunci yang tidak dapat di-null tanpa nilai default yang parameter URI terkaitnya tidak ada dalam URI permintaan.
- Kegagalan penguraian jenis: Nilai argumen yang diekstrak yang tidak dapat diuraikan
menjadi jenis properti yang diharapkan (misalnya,
"abc"untuk propertiInt).
Jika terjadi ketidakcocokan, UriDeepLinkMatcher.match akan menampilkan null, sehingga
pencocok berikutnya dapat dievaluasi.
Pertimbangkan class kunci dan matcher yang dikonfigurasi dengan nilai default, objek bertingkat, dan 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>() )
Tabel berikut menunjukkan hasil pencocokan untuk berbagai URI permintaan:
| URI Permintaan | Hasil Decoding | Hasil Pertandingan |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Berhasil (Semua parameter diberikan) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
Berhasil (zoom ditetapkan secara default ke 12, layer ke STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Berhasil (Parameter kueri opsional kosong menggunakan 12 default) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
Tidak cocok (Parameter location yang diperlukan tidak ada) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
Tidak cocok ("close" bukan Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
Tidak cocok ("HYBRID" tidak ada dalam enum) |
null |
Konfigurasi yang tidak didukung
Jika class kunci Anda berisi jenis data yang tidak didukung, UriDeepLinkMatcher akan menampilkan
pengecualian selama pencocokan, bukan menampilkan null.
- Peta dan koleksi multi-dimensi:
UriDeepLinkMatcherhanya mendukung koleksi satu dimensi dari jenis primitif, string, enum, atau jenis kustom yang dianotasi denganDeepLinkSerializer. JenisMapmenampilkanIllegalArgumentException, sedangkan koleksi bertingkat (sepertiList<List<String>>) menampilkanSerializationException. - Koleksi objek kustom tanpa anotasi: Koleksi jenis kustom (seperti
List<Filter>) menampilkanSerializationExceptionkecuali jika jenis elemen diberi anotasi denganDeepLinkSerializer. - Class bertingkat yang tidak di-unflatten: Class
@Serializablebertingkat tidak dapat dipetakan ke satu placeholder (seperti?user={user}) tanpaDeepLinkSerializer.
// 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
Perbandingan UriMatchResult
Instance UriMatchResult diberi peringkat menggunakan kriteria berikut secara berurutan:
- Jenis MatchResult:
UriMatchResultmemiliki peringkat lebih tinggi daripada jenisMatchResultlainnya. - Jalur persis: Kecocokan jalur literal memiliki peringkat lebih tinggi daripada kecocokan placeholder atau karakter pengganti.
- Jumlah argumen jalur: Kecocokan dengan lebih banyak argumen jalur akan memiliki peringkat lebih tinggi.
- Keberadaan argumen: Pencocokan yang menangkap argumen memiliki peringkat lebih tinggi daripada yang tidak.
- Jumlah total argumen: Jumlah total argumen (jalur, kueri, fragmen) adalah penentu akhir.
Sesuaikan UriDeepLinkMatcher
UriDeepLinkMatcher adalah class open yang dapat Anda buat subclass-nya untuk menyesuaikan perilaku pencocokan URI dan ekstraksi argumen:
matchRequest: Titik entri pencocokan tingkat teratas untukDeepLinkRequestyang masuk. Ganti ini untuk memeriksa tambahan permintaan atau menerapkan prasyarat kustom sebelum pencocokan URI.matchUri: MencocokkanDeepLinkUridengan pola yang dikonfigurasi. Ganti ini untuk menangkap dan menormalisasi URI yang masuk (misalnya, menulis ulang subdomain dinamis atau format jalur lama) sebelum memanggilsuper.matchUri.matchArguments: Melakukan deserialisasi peta argumen jalur, kueri, dan fragmen yang diekstrak menjadi instance kunci navigasi menggunakanserializeryang disediakan. Ganti ini untuk memasukkan nilai dinamis atau mengubah argumen sebelum pembuatan instance kunci.
Contoh berikut menunjukkan subclassing UriDeepLinkMatcher untuk menormalisasi
awalan jalur URL lama sebelum pencocokan:
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) } }