หากต้องการจับคู่ URI แบบลำดับชั้นกับรูปแบบและดึงอาร์กิวเมนต์ ให้ใช้ UriDeepLinkMatcher โดยจะใช้ kotlinx.serialization เพื่อดีซีเรียลไลซ์อาร์กิวเมนต์ที่ตรงกันเป็นคลาสคีย์
หากต้องการสร้าง UriDeepLinkMatcher ให้ระบุรูปแบบ DeepLinkUri และ Serializer สำหรับคีย์ที่เกี่ยวข้อง ดังนี้
@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:) โปรดดูหัวข้อสร้างตัวจับคู่ Deep Link ที่กำหนดเอง
รูปแบบการจับคู่ที่รองรับ
UriDeepLinkMatcher จับคู่ URI ตามคอมโพเนนต์ 5 รายการ ได้แก่ รูปแบบ, หน่วยงาน, เส้นทาง, การค้นหา และส่วนย่อย ส่วนต่อไปนี้จะอธิบายไวยากรณ์รูปแบบที่รองรับ ตัวยึดตำแหน่งอาร์กิวเมนต์ และกฎการจับคู่สำหรับแต่ละคอมโพเนนต์
การจับคู่รูปแบบ
หากไม่มีรูปแบบในรูปแบบ URI ระบบจะจับคู่ทั้ง http และ https
หากต้องการจับคู่รูปแบบที่เฉพาะเจาะจง ให้รวมรูปแบบนั้นไว้ในรูปแบบ ข้อยกเว้นคือรูปแบบ
http ในรูปแบบจะตรงกับทั้ง URI คำขอ http และ https ส่วน
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 |
✅ |
การจับคู่หน่วยงาน
UriDeepLinkMatcher จะทำการจับคู่แบบตรงทั้งหมดโดยไม่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ในหน่วยงาน URI (โฮสต์และพอร์ตที่ไม่บังคับ) ระบบไม่รองรับตัวยึดตำแหน่งหรือไวลด์การ์ดในหน่วยงาน และไม่มีการดึงอาร์กิวเมนต์ออกมา
| 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" |
การจับคู่แฟรกเมนต์
ระบบรองรับรูปแบบ Fragment ประเภทต่อไปนี้
| 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เพื่อแปลงกลับเป็นอนุกรมเป็น- Primitives (
Boolean,Int,Long,Float,Double,Char,Byte,Short) และString - Enums
Set,ListหรือArrayของพรีมิติฟ สตริง หรือ Enum- คลาส
@Serializableที่ซ้อนกัน (ซึ่งพร็อพเพอร์ตี้จะถูกรวมเป็นตัวยึดตำแหน่ง URI แต่ละรายการ)
- Primitives (
- การแปลงเป็นอนุกรมที่กำหนดเองด้วย
DeepLinkSerializer: แปลงระหว่างStringรายการเดียวกับออบเจ็กต์ที่กำหนดเอง ประเภทภายนอก (เช่นjava.time.LocalDate) หรือคอลเล็กชันที่คั่นด้วยตัวคั่นที่กำหนดเอง
การเรียงอันดับแบบมาตรฐาน
UriDeepLinkMatcher ทำงานได้ทันทีสำหรับประเภทมาตรฐานและโครงสร้างแบบแบนราบโดยไม่ต้องใช้การติดตั้งใช้งาน Serializer ที่กำหนดเอง
แบบพื้นฐานและสตริง
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)
Enums
ค่า 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 ซึ่งช่วยให้สามารถประเมินตัวจับคู่ที่ตามมาได้
พิจารณาคลาสคีย์และ Matcher ที่กำหนดค่าด้วยค่าเริ่มต้น ออบเจ็กต์ที่ฝัง และ 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ประเภทจะแสดงIllegalArgumentExceptionขณะที่คอลเล็กชันที่ฝัง (เช่น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: ดีซีเรียลไลซ์เส้นทาง การค้นหา และอาร์กิวเมนต์ Fragment ที่แยกออกมาเป็นอินสแตนซ์คีย์การนำทางโดยใช้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) } }