หากต้องการจับคู่ URI แบบลำดับชั้นกับรูปแบบและแยกอาร์กิวเมนต์ ให้ใช้
UriDeepLinkMatcher ซึ่งจะใช้ kotlinx.serialization เพื่อแยกซีเรียลไลซ์อาร์กิวเมนต์ที่ตรงกันเป็นคีย์คลาส
หากต้องการสร้าง UriDeepLinkMatcher ให้ระบุรูปแบบ DeepLinkUri และ
ซีเรียลไลเซอร์สำหรับคีย์ที่เกี่ยวข้อง
@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")
สำหรับ URI แบบไม่ลำดับชั้นหรือแบบแผนที่กำหนดเอง (เช่น tel:) โปรดดู
หัวข้อสร้างตัวจับคู่ดีปลิงก์ที่กำหนดเอง
รูปแบบการจับคู่ที่รองรับ
UriDeepLinkMatcher จะจับคู่ URI ตามคอมโพเนนต์ 5 รายการ ได้แก่ แบบแผน, Authority, เส้นทาง, การค้นหา และส่วนย่อย ส่วนต่อไปนี้จะอธิบายไวยากรณ์รูปแบบที่รองรับ ตัวยึดตำแหน่งอาร์กิวเมนต์ และกฎการจับคู่สำหรับคอมโพเนนต์แต่ละรายการ
การจับคู่แบบแผน
หากไม่มีแบบแผนในรูปแบบ URI ระบบจะจับคู่ทั้ง http และ https
หากต้องการจับคู่แบบแผนที่เฉพาะเจาะจง ให้ใส่แบบแผนนั้นในรูปแบบ โดยมีข้อยกเว้นว่าแบบแผน
http ในรูปแบบจะจับคู่ทั้ง http และ https URI ของคำขอ ขณะที่
https ในรูปแบบจะจับคู่คำขอ https เท่านั้น
| URI รูปแบบ | URI คำขอ | การจับคู่ที่ตรงกัน |
|---|---|---|
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 |
✅ |
การจับคู่ Authority
UriDeepLinkMatcher จะทำการจับคู่ Authority (โฮสต์และพอร์ตที่ไม่บังคับ) ของ URI แบบตรงทั้งหมดโดยไม่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ ระบบไม่รองรับตัวยึดตำแหน่งหรือไวลด์การ์ดใน Authority และจะไม่มีการแยกอาร์กิวเมนต์
| URI รูปแบบ | URI คำขอ | การจับคู่ที่ตรงกัน |
|---|---|---|
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 |
❌ |
การจับคู่เส้นทาง
ระบบรองรับรูปแบบเส้นทางต่อไปนี้
| URI รูปแบบ | URI คำขอ | การจับคู่ที่ตรงกัน | อาร์กิวเมนต์ที่แยกออกมา |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | ไม่มี |
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: "" (สตริงที่ว่างเปล่า) |
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 |
✅ | ไม่มี |
www.example.com/users |
https://www.example.com/users/ |
❌ (เครื่องหมายทับปิดท้ายจะสร้างเซกเมนต์เพิ่มเติม) | ไม่มี |
การจับคู่การค้นหา
ลำดับพารามิเตอร์การค้นหาใน URI ของคำขอไม่จำเป็นต้องตรงกับลำดับใน URI รูปแบบ นอกจากนี้ ระบบจะละเว้นพารามิเตอร์ที่อยู่ใน URI ของคำขอแต่ไม่อยู่ใน URI รูปแบบ
ระบบรองรับรูปแบบพารามิเตอร์การค้นหาต่อไปนี้
| URI รูปแบบ | URI คำขอ | อาร์กิวเมนต์ที่แยกออกมา |
|---|---|---|
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: "" (สตริงที่ว่างเปล่า) |
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" |
การจับคู่ส่วนย่อย
ระบบรองรับประเภทรูปแบบส่วนย่อยต่อไปนี้
| URI รูปแบบ | URI คำขอ | อาร์กิวเมนต์ที่แยกออกมา |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
ไม่มี |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
ไม่มี |
ประเภทข้อมูลที่รองรับ
UriDeepLinkMatcher รองรับการแยกซีเรียลไลซ์อาร์กิวเมนต์ URI เป็นประเภทข้อมูลพื้นฐาน, Enum, คอลเล็กชัน และออบเจ็กต์ที่กำหนดเอง การแยกซีเรียลไลซ์แบ่งออกเป็น 2 หมวดหมู่
- การแยกซีเรียลไลซ์มาตรฐาน: ใช้
kotlinx.serializationเพื่อแยกซีเรียลไลซ์ เป็นประเภทข้อมูลต่อไปนี้:- ประเภทข้อมูลพื้นฐาน (
Boolean,Int,Long,Float,Double,Char,Byte,Short) และString - Enum
Set,ListหรือArrayของประเภทข้อมูลพื้นฐาน, สตริง หรือ Enum- คลาส
@Serializableที่ซ้อนกัน (ซึ่งพร็อพเพอร์ตี้จะถูกทำให้แบนราบเป็นตัวยึดตำแหน่ง URI แต่ละรายการ)
- ประเภทข้อมูลพื้นฐาน (
- การแยกซีเรียลไลซ์ที่กำหนดเองด้วย
DeepLinkSerializer: แปลงระหว่างStringเดียวกับออบเจ็กต์ที่กำหนดเอง, ประเภทภายนอก (เช่นjava.time.LocalDate) หรือคอลเล็กชันที่คั่นด้วยตัวคั่นที่กำหนดเอง
การแยกซีเรียลไลซ์มาตรฐาน
UriDeepLinkMatcher ใช้งานได้ทันทีสำหรับประเภทมาตรฐานและโครงสร้างที่ทำให้แบนราบโดยไม่ต้องมีการติดตั้งใช้งานซีเรียลไลเซอร์ที่กำหนดเอง
ประเภทข้อมูลพื้นฐานและสตริง
UriDeepLinkMatcher จะถอดรหัสประเภทข้อมูลพื้นฐาน (Boolean, Int, Long, Float, Double, Char, Byte, Short) และ 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
ระบบจะจับคู่ค่า Enum กับชื่อองค์ประกอบ 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)
คอลเล็กชันการค้นหาที่ซ้ำกัน
พารามิเตอร์การค้นหาที่มีคีย์ซ้ำกัน (เช่น ?id=10&id=20) จะแยกซีเรียลไลซ์เป็น List<T>, Set<T>, หรือ Array<T> โดยอัตโนมัติ ซึ่ง T เป็นประเภทข้อมูลพื้นฐาน, String, หรือ 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))
คลาส @Serializable ที่ซ้อนกัน
เมื่อ NavKey มีพร็อพเพอร์ตี้ที่มีประเภทเป็นคลาส @Serializable อื่น UriDeepLinkMatcher จะทำให้พร็อพเพอร์ตี้แบนราบเพื่อให้พร็อพเพอร์ตี้แต่ละรายการของคลาสที่ซ้อนกันแมปกับพารามิเตอร์ URI แต่ละรายการที่มีชื่อเดียวกันโดยตรง
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))
การแยกซีเรียลไลซ์ที่กำหนดเองด้วย DeepLinkSerializer
หากต้องการแยกซีเรียลไลซ์ออบเจ็กต์ที่กำหนดเอง (เช่น Filter(key = "brand", value =
"pixel")), ประเภทภายนอก (เช่น java.time.LocalDate) หรือสตริงที่คั่นด้วยตัวคั่นที่กำหนดเอง (เช่น ค่าที่คั่นด้วยคอมมา) ให้ขยาย DeepLinkSerializer<T>
DeepLinkSerializer<T> เป็น KSerializer<T> แบบแอบสแตรกต์ที่แปลง
ระหว่าง String กับ T
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
ตัวอย่างเช่น พิจารณาคำจำกัดความ Filter และ FilterSerializer ที่
ใช้ในข้อมูลโค้ดต่อไปนี้
@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}" }
ออบเจ็กต์ที่กำหนดเองรายการเดียว
หากต้องการถอดรหัสออบเจ็กต์จากสตริงพารามิเตอร์ URI เดียว (เช่น ?filter=brand:google) ให้ใส่คำอธิบายประกอบพร็อพเพอร์ตี้ด้วย @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"))
ออบเจ็กต์ที่กำหนดเองในพารามิเตอร์การค้นหาที่ซ้ำกัน
หากต้องการแยกซีเรียลไลซ์พารามิเตอร์การค้นหาที่ซ้ำกันเป็นคอลเล็กชันออบเจ็กต์ที่กำหนดเอง
(List<T>, Set<T>, หรือ Array<T>) ให้ติดตั้งใช้งาน DeepLinkSerializer<T> สำหรับ
ประเภทองค์ประกอบ T และใส่คำอธิบายประกอบอาร์กิวเมนต์ประเภทของพร็อพเพอร์ตี้ด้วย
@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")))
คอลเล็กชันที่คั่นด้วยตัวคั่นในพารามิเตอร์เดียว
หากต้องการแยกวิเคราะห์ค่าที่คั่นด้วยคอมมาหรือตัวคั่นที่กำหนดเอง (เช่น ?ids=1,2,3) เป็นคอลเล็กชัน ให้ติดตั้งใช้งาน DeepLinkSerializer สำหรับประเภทคอลเล็กชันทั้งหมด และใส่คำอธิบายประกอบพร็อพเพอร์ตี้ด้วย @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))
การตรวจสอบความถูกต้องของอาร์กิวเมนต์และผลการจับคู่
UriDeepLinkMatcher จะแยกความแตกต่างระหว่างการไม่ตรงกัน (แสดงผล null เพื่อให้ลองใช้ตัวจับคู่อื่นๆ ได้) กับการกำหนดค่าที่ไม่รองรับ (แสดงข้อยกเว้น)
การไม่ตรงกัน
การไม่ตรงกันจะเกิดขึ้นเมื่อ URI ของคำขอขาเข้าไม่ตรงตามข้อกำหนดของรูปแบบหรือประเภท
- ไม่มีพารามิเตอร์ที่จำเป็น: พร็อพเพอร์ตี้คีย์ที่ไม่เป็น Null ซึ่งไม่มีค่าเริ่มต้น และไม่มีพารามิเตอร์ URI ที่เกี่ยวข้องใน URI ของคำขอ
- การแยกวิเคราะห์ประเภทล้มเหลว: ค่าอาร์กิวเมนต์ที่แยกออกมาซึ่งแยกวิเคราะห์เป็นประเภทพร็อพเพอร์ตี้ที่คาดไว้ไม่ได้ (เช่น
"abc"สำหรับพร็อพเพอร์ตี้Int)
เมื่อเกิดการไม่ตรงกัน UriDeepLinkMatcher.match จะแสดงผล null ซึ่งช่วยให้ประเมินตัวจับคู่ที่ตามมาได้
พิจารณาคีย์คลาสและตัวจับคู่ที่กำหนดค่าด้วยค่าเริ่มต้น ออบเจ็กต์ที่ซ้อนกัน และ 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>() )
ตารางต่อไปนี้แสดงผลการจับคู่สำหรับ URI ของคำขอต่างๆ
| URI คำขอ | ผลการถอดรหัส | ผลการจับคู่ |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
สำเร็จ (ระบุพารามิเตอร์ทั้งหมด) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
สำเร็จ (zoom เป็นค่าเริ่มต้น 12, layer เป็น STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
สำเร็จ (พารามิเตอร์การค้นหาที่ไม่บังคับที่ว่างเปล่าใช้ค่าเริ่มต้น 12) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
ไม่ตรงกัน (ไม่มีพารามิเตอร์ location ที่จำเป็น) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
ไม่ตรงกัน ("close" ไม่ใช่ Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
ไม่ตรงกัน ("HYBRID" ไม่อยู่ใน Enum) |
null |
การกำหนดค่าที่ไม่รองรับ
หากคีย์คลาสมีประเภทข้อมูลที่ไม่รองรับ UriDeepLinkMatcher จะแสดงข้อยกเว้นระหว่างการจับคู่แทนที่จะแสดงผล null
- แผนที่และคอลเล็กชันหลายมิติ:
UriDeepLinkMatcherรองรับเฉพาะ คอลเล็กชันแบบมิติเดียวของประเภทข้อมูลพื้นฐาน, สตริง, Enum หรือ ประเภทที่กำหนดเองซึ่งใส่คำอธิบายประกอบด้วยDeepLinkSerializerMapประเภทจะแสดง anIllegalArgumentExceptionขณะที่คอลเล็กชันที่ซ้อนกัน (เช่นList<List<String>>) จะแสดงSerializationException - คอลเล็กชันออบเจ็กต์ที่กำหนดเองซึ่งไม่มีคำอธิบายประกอบ: คอลเล็กชันของประเภทที่กำหนดเอง (เช่น
เช่น
List<Filter>) จะแสดงSerializationExceptionเว้นแต่ว่าประเภทองค์ประกอบ จะใส่คำอธิบายประกอบด้วยDeepLinkSerializer - คลาสที่ซ้อนกันซึ่งไม่ได้ทำให้แบนราบ: คลาสที่ซ้อนกัน
@Serializableจะแมปกับตัวยึดตำแหน่งเดียว (เช่น?user={user}) ไม่ได้หากไม่มีDeepLinkSerializer
// 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
การเปรียบเทียบ UriMatchResult
ระบบจะจัดอันดับอินสแตนซ์ UriMatchResult โดยใช้เกณฑ์ต่อไปนี้ตาม
ลำดับ
- ประเภท MatchResult:
UriMatchResultมีอันดับสูงกว่าMatchResultประเภทอื่นๆ - เส้นทางที่ตรงทั้งหมด: การจับคู่เส้นทางแบบตรงทั้งหมดมีอันดับสูงกว่าการจับคู่ตัวยึดตำแหน่งหรือ ไวลด์การ์ด
- จำนวนอาร์กิวเมนต์เส้นทาง: การจับคู่ที่มีอาร์กิวเมนต์เส้นทางมากกว่าจะมีอันดับสูงกว่า
- การมีอาร์กิวเมนต์: การจับคู่ที่บันทึกอาร์กิวเมนต์ได้จะมีอันดับสูงกว่า การจับคู่ที่บันทึกอาร์กิวเมนต์ไม่ได้
- จำนวนอาร์กิวเมนต์ทั้งหมด: จำนวนอาร์กิวเมนต์ทั้งหมด (เส้นทาง, การค้นหา, ส่วนย่อย) เป็นตัวตัดสินสุดท้าย
ปรับแต่ง UriDeepLinkMatcher
UriDeepLinkMatcher เป็นคลาส open ที่คุณสามารถสร้างคลาสย่อยเพื่อปรับแต่งลักษณะการทำงานของการจับคู่ URI และการแยกอาร์กิวเมนต์ได้
matchRequest: จุดเริ่มต้นการจับคู่ระดับบนสุดสำหรับDeepLinkRequestขาเข้า ลบล้างฟังก์ชันนี้เพื่อตรวจสอบข้อมูลเพิ่มเติมของคำขอหรือใช้เงื่อนไขเบื้องต้นที่กำหนดเองก่อนการจับคู่ URImatchUri: จับคู่DeepLinkUriกับรูปแบบที่กำหนดค่าไว้ ลบล้างฟังก์ชันนี้เพื่อสกัดกั้นและทำให้ URI ขาเข้าเป็นมาตรฐาน (เช่น การเขียนโดเมนย่อยแบบไดนามิกหรือรูปแบบเส้นทางเดิมใหม่) ก่อนเรียกsuper.matchUrimatchArguments: แยกซีเรียลไลซ์แผนที่อาร์กิวเมนต์เส้นทาง, การค้นหา และส่วนย่อยเป็นอินสแตนซ์คีย์การนำทางโดยใช้serializerที่ระบุ ลบล้างฟังก์ชันนี้เพื่อแทรกค่าแบบไดนามิกหรือแปลงอาร์กิวเมนต์ก่อนการสร้างอินสแตนซ์คีย์
ตัวอย่างต่อไปนี้แสดงการสร้างคลาสย่อย UriDeepLinkMatcher เพื่อทำให้คำนำหน้าเส้นทาง URL เดิมเป็นมาตรฐานก่อนการจับคู่
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) } }