Yazılım Mühendisi
Modüler Android uygulamalarında derin bağlantıları yönetmek, kolayca bozuk rotalara ve çalışma zamanında sürprizlere yol açabilir.Bu oturumda, sağlam ve ölçeklenebilir bir rota sistemi oluşturmanıza yardımcı olmak için Jetpack Navigation3'ün Compose için yeni, tür güvenli derin bağlantı mimarisi ele alınacak. Kullanıma hazır derin bağlantı eşleştirme API'lerini nasıl uygulayacağınızı, karmaşık tip güvenliği bağımsız değişkenleri için özel serileştiriciler yazmayı ve kullanıcı gezinmesini öngörülebilir kılan dinamik hiyerarşik eski yığınlar oluşturmayı öğreneceksiniz.
DeepLinkRequest sınıfı, Android Intent oluşturucu dahil olmak üzere girişi kapsar.UriDeepLinkMatcher, URI kalıplarını eşleştirmek ve bağımsız değişkenleri tür açısından güvenli NavKey özelliklerine dönüştürmek için Kotlin serileştirmesini kullanır. İlkel türler otomatik olarak çalışır ve değerler ayrıştırılamazsa eşleşmeler başarısız olur.DeepLinkSerializer sınıfını genişletebilir veya matchArguments yöntemini geçersiz kılabilir. UriDeepLinkMatcher alt sınıfını oluşturarak Kotlin serileştirme işlemi çalıştırılmadan önce ham URI yer tutucularını önceden işleyebilirsiniz.withBackStack çağrısı, en iyi eşleşme için sentetik eski yığınları geç oluşturur. Bu sayede, diğer tüm geçerli eşleşmeler için gereksiz eski yığınlar hesaplanmadan sistemin geri düğmesi davranışı doğru şekilde sağlanır.Transkript
Merhaba, ben Ian Lake. Jetpack Architecture ekibinin mühendislik müdürüyüm.
Ben de Clara Fok, Nav 3'ün teknik lideriyim.
Bugün, benim için çok önemli bir özellik olan derin bağlantılardan bahsedeceğiz.
Hatta bu özellikleri o kadar çok beğendim ki Nav3'ün 1.2 sürümünün bir parçası olarak hem Nav2 hem de Nav3 için uygulama yaptım.
Sanırım bunu üç kez yaptınız çünkü Nav 3 tariflerinde de bir uygulama yaptık.
Evet, bu doğru.
Nav3'ün Compose öncelikli yaklaşımına uygun olduğundan emin olmak için API'de birçok iterasyon yaptık.
Bu başarının büyük bir kısmı, tariflere verdiğimiz önemle başladı.
Evet, Navigasyon 3'teki temel hedeflerimizden biri, size engel olmamaktı.
Özellikle iddialı bir özellik oluşturmak istediğinizde, bunu mümkün kılmak için bizden özellik isteğinde bulunmanız gerekmez.
"Peki, bunu nasıl yapacağım?" sorusuyla karşılaştığımızda
Ya da "Bu soruna nasıl yaklaşabilirim?" diye düşünebilirsiniz.
En başından özel bir API oluşturmak yerine, Navigation 3'te herhangi bir değişiklik yapmanıza gerek kalmadan bu özelliği uygulamanıza nasıl ekleyebileceğinizi açıklayan bir kılavuz hazırladık.
Söylemek yerine göstermeyi tercih ettiğimiz için GitHub'da Nav3 tarifleri deposunu oluşturduk.
Artık birden fazla eski yığın ve alt sayfa gibi yaygın kullanım alanları için 35'ten fazla tarif içeriyor. Bu tarifler, uygulamanızda kullanmanız gereken tüm kodları sağlıyor.
Bu sayede, Kasım 2025'te Nav3'ün 1.0 sürümü yayınlandığında bile derin bağlantıları uygulayacak bir yöntemimiz vardı. Çünkü bunun çoğunuz için önemli olduğunu biliyorduk.
Ancak bir tarifin, uygulamanıza kopyalayıp yapıştırmanız gereken bir dizi yardımcı yöntem gerektirdiğini fark ettiğimizde, "mezuniyet" adı verilen bir süreci değerlendiririz. Bu süreçte, bu yardımcıları alır ve "Peki, bunlar Navigation 3'ün bir parçası olsaydı nasıl görünürdü?" diye sorarız.
Bu sayede, kodu kopyalayıp yapıştıran geliştiriciler sorun yaşamaz.
Bu kodu saklayabilirsiniz.
Özel çözümleriniz varsa ve bunlar işe yarıyorsa harika.
Zorunlu taşıma işleminiz yok.
Ancak bu sizin için yepyeni bir sorun veya yeni bir gereksinimse, bunu doğrudan navigasyonun bir parçası olarak kullanmanıza olanak tanıyan kolay ve onaylanmış bir çözüm elde edersiniz.
Ancak benim için en iyi kısım, bu yardımcıyı ekledikten sonra geri dönüp tariflerimizi güncellememiz ve kod silmenin verdiği o tatlı dopamin hissini yaşamamız.
Böylece, bu sorun için önerilen yaklaşım türüne yönelik tariflere bakan herkes en güncel çözüme sahip olur.
Tabii ki sonradan akıl vermek kolaydır. Bu mezuniyet süreci, tariflerde oluşturduğumuz yardımcı araçlara gerçekten bakıp "Bunlar gerçekten en iyi API'ler mi?" diye sorma fırsatı veriyor.
Bu durumda, kitaplığa taşırken bu öğeleri iyileştirme fırsatımız oluyor.
Derin bağlantılar için de durum kesinlikle böyleydi.
Teşekkürler Ian.
Bu nedenle, kavramsal olarak hem tarifler hem de son API'lerimiz birbirine çok benzer. Çünkü Nav 3'te eski yığın size aittir.
Yani derin bağlantı API'lerimizin odak noktası, ilk eski yığının ne olması gerektiğini, özellikle de kullanıcılarınız uygulamanıza girmek için bir derin bağlantıyı tıkladığında hangi ekranı göreceğini belirlemektir.
Peki kullanıcı bu web URI'sini tıkladığında gerçekte ne olur?
Artık Nav3 kullansanız da kullanmasanız da Android çerçevesi, uygulamanızın desteklediği her derin bağlantı için intent filtreleri beyan etmenizi gerektirir.
Şimdi, asıl ilginç olan kısma, yani derin bağlantı etkinliğinizi başlattıktan sonra ne olduğuna bakalım.
Burada giriş, uygulamanızın başlatıldığı Intent'tir, yani başlatılan Etkinlik'tir.
Ancak derin bağlantı birçok biçimde olabilir.
Örneğin, bir test için manuel olarak oluşturmak veya Kotlin Multiplatform desteğinizden yararlanmak isteyebilirsiniz.
Bu nedenle, derin bağlantı girişini KMP uyumlu bir DeepLinkRequest sınıfına kapsülledik.
Android'de ise Android Intent'i alan bir DeepLinkRequest oluşturucumuz var.
Sonraki hedefimiz, bir istek verildiğinde NavKey öğesinin eski yığınınıza eklenmesi gerektiğini nasıl anlayacağımızı bulmaktır.
İşte bu noktada DeepLinkMatcher sınıfımız devreye giriyor.
Bir istek alır ve eşleşmeyle ilişkili NavKey değerini içeren bir MatchResult döndürür.
Özellikle web URI'leri için, belirli bir isteği MatchResult değerine dönüştürmek için gereken tüm mantığı kapsayan bir UriDeepLinkMatcher var.
val matcher = UriDeepLinkMatcher(
...,
...
)UriDeepLinkMatcher'nin aldığı ilk parametre bir URI kalıbıdır.
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri(...),
...
)Örneğin, bir ADS uygulaması geliştiriyorsunuz ve ana ekrana derin bağlantı eklemek istiyorsunuz.
Burada kalıp tam bir URI'dır ve hem HTTP hem de HTTPS ile otomatik olarak eşleşmesi için HTTP şemasını çıkarabiliriz.
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/home"),
...
)Ancak belirli bir reklam oturumuna derin bağlantı oluşturmak istiyorsak bu derin bağlantı, ayıklamamız gereken bilgileri (ör. oturum kimliği) içerir.
// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
…
)Bu durumda, kalıbımız derin bağlantıdan çıkarmamız gereken bilgiler için yer tutucu olarak süslü parantezler kullanır.
Her türlü girişi sınıf örneklerine dönüştürmek için kullanışlı bir kitaplık olduğu ortaya çıktı. Bu kitaplık, Kotlin Serialization.
UriDeepLinkMatcher, giriş URI'sini seri durumdan çıkarmak ve NavKey örneği oluşturmak için Kotlin kod çözücüsünü kullanır.
// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
serializer = serializer<Session>()
)Bu kod çözücünün hangi sınıfla çalışacağını bilmesi gerekir. Bu nedenle, eşleştiriciye NavKey için bir serileştirici de iletmeniz gerekir.
// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("http://ads.android.com/session/{id}"),
serializer = serializer<Session>()
)Kod çözücünün, yer tutucuları anahtarın özellikleriyle doğru şekilde eşleştirmesine yardımcı olmak için yer tutucunun adı, özellik adlarıyla eşleşmelidir.
Bu örnekte, özellik adı, oturumun kimlik parametresiyle eşleşen kimliktir.
UriDeepLinkMatcher oluşturulduğuna göre gelen derin bağlantıyla bir DeepLinkRequest oluşturmamız gerekiyor.
// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("http://ads.android.com/session/{id}"),
serializer = serializer<Session>()
)
val request = DeepLinkRequest(
uri = DeepLinkUri("http://ads.android.com/session/abcd")
)
val matchResult: UriMatchResult<Session>? = matcher.match(request)
val backStack = listOf(matchResult?.key ?: Home)Ardından, eşleştirme işlevi çağrılarak ve isteğimiz iletilerek eşleştirme yapılır.
Bu işlem, NavKey değerini içeren ve null değer alabilen bir MatchResult döndürür. Örneğimizde bu değer oturum anahtarıdır. Sonuç null ise varsayılan olarak ana ekrana dönebiliriz.
Artık birden fazla derin bağlantı, birden fazla eşleştirici olarak yazılmalıdır: her derin bağlantı için bir eşleştirici.
Örneğin, burada oturum anahtarımız için iki URI kalıbı var: biri eski bülten kalıbı, diğeri ise normal web URI'si için.
Ayrıca ADS ana ekranına giden bir derin bağlantımız da var. Bu nedenle, bunun için üçüncü bir eşleştirici ekliyoruz.
Tüm eşleştiriciler, üzerinde işlem yaptığımız aynı listeye eklenir.
Böylece eşleşmemiz, listemizdeki tüm eşleştiriciler üzerinde bir döngü haline gelir. Burada, istekle eşleşen tüm eşleştiricileri bulmak için her birinde eşleşmeyi çağırırız.
Bunun için mapNotNull kullanarak eşleşmeyenleri filtreleyebilir ve yalnızca geçerli eşleşmelerin listesini alabiliriz.
val matchers = listOf(...)
// gets all non-null MatchResult
val matchResults = matchers.mapNotNull { matcher ->
matcher.match(request)
}Çoğu durumda, listenizde muhtemelen yalnızca bir tane boş olmayan eşleşme olur ancak birden fazla geçerli eşleşmenin olabileceği durumlar da vardır.
Örneğin, aynı oturum URI kalıbını yeniden kullanan özel bir açılış konuşması ekranı sunduğumuzu ancak artık kimlik yer tutucusu yerine statik bir açılış konuşması yoluna sahip olduğunu varsayalım.
Bu durumda, aynı istek her iki eşleştiriciyle de eşleşebilir: kimliğin keynote'a eşit olduğu oturum anahtarı ve keynote NavKey.
MatchResult, Comparable'ı uyguladığı için max işlevini kullanarak listedeki tüm geçerli eşleşmeleri karşılaştırarak en iyi eşleşmeyi bulabiliriz. İstekle eşleşen derin bağlantıların olmadığı durumu ele almak için maxOrNull kullanabiliriz.
val matchers = listOf(...)
// gets all non-null MatchResult
val matchResults = matchers.mapNotNull { matcher ->
matcher.match(request)
}
// compare matches
val backStack = listOf(
matchResults.maxOrNull()?.key ?: Home
)Kısa bir sunum için statik bir eşleştirici listesi kullanmak mantıklı olabilir. Ancak ekranlarınızın birden fazla modüle yayılabilmesi gibi, bu modüllerin de DI sisteminiz aracılığıyla global bir eşleştirici listesine katkıda bulunmasını bekleriz.
// Feature A Module
@ContributesTo(ActivityScope::class)
@BindingContainer
object FeatureAModule {
@IntoSet
@Provides
fun provideDeepLinkMatcher() : UriDeepLinkMatcher<NavKey> =
UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/featureA"),
serializer<FeatureBKey>(),
)
}
// in MainActivity
@Inject lateinit var matchers: Set<UriDeepLinkMatcher<NavKey>>Bu yaklaşımı kullanarak tarif depomuzdaki Hilt, Metro ve Koin tariflerimizi derin bağlantılar içerecek şekilde güncelledik.
Clara, UriDeepLinkMatcher uygulamamızın derin bağlantı isteğinden alanları ayıklamak ve bunları gösterdiğimiz oturum sınıfı gibi tür güvenli sınıfımıza dönüştürmek için Kotlin Serialization kullandığını belirtmişti.
Burada yazıldığı gibi, bu oldukça basit bir dönüşümdür.
URI bir dizedir.
Alan bir dizedir.
Vay canına, ne kadar kolaymış.
Ancak yalnızca dize özelliklerinin ötesine geçmemiz gerektiğini biliyorduk.
Kotlin Serialization, bu konuda bize sağlam bir temel sunar.
Boole, tam sayı, kayan nokta ve hatta Enum gibi tüm temel türler, derin bağlantı değeri söz konusu türe göre ayrıştırılamazsa eşleşmeyi otomatik olarak başarısız kılar ve null değerini döndürür.
Örneğin, oturumumuzun kimlik parametresi dize yerine tamsayı olsaydı tamsayı olarak ayrıştırılamayan bir dize iletildiğinde eşleşme başarısız olurdu.
// pattern = "http://ads.android.com/session/{id}"
@Serializable data class Session(val id: Int) : NavKeyBenzer şekilde, isteğe bağlı bağımsız değişkenleriniz (ör. web URL'nize eklenen analiz verileri) varsa bunlar, anahtarınızda varsayılan değerleri olan özellikler olabilir.
// pattern = "http://ads.android.com/session/{id}#referrer={referrer}
@Serializable data class Session(
val id: Int,
val referrer: String? = null,
) : NavKeyBurada, web URL'nizin yapısını, uygulamanızdaki derin bağlantı eşleşmesini temsil eden tip güvenliği sınıflarından veya sınıflardan ayırmak için çok çaba sarf ettiğimizi göreceksiniz. Bu yapı, farklı bir ekibe ait olabilir.
Çünkü bazılarınızın son derece kötü derin bağlantılarla uğraştığını biliyorum.
// "id={id}&base64={placeholderImage}"
@Serializable data class Session(
val id: String,
val placeholderImage: Bitmap? = null
) : NavKey
// "jsonBlob={user}"
@Serializable data class Profile(
val user: User,
) : NavKeyÖrneğin, derin bağlantınıza Base64 kodlu bir resim eklenmiş olabilir.
Örneğin, "Bunu uygulamanızın her URL'sine ekleyelim" diyerek devasa bir JSON blob'u eklemiş olabilirler.
Size verilen web URL'sini değiştiremediğiniz ancak URL'den bazı bilgileri çıkarmanız gereken durumlarda artık bir çözüm yolu var.
İlk teknik, bu bağımsız değişkenin tam olarak nasıl seri durumdan çıkarılacağını veya bir dizeden tür güvenli mülkünüze nasıl dönüştürüleceğini kontrol etmek için DeepLinkSerializer sınıfımızı genişletmektir.
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
...
}DeepLinkSerializer, Kotlin serileştirme için yardımcı bir sarmalayıcıdır. Bu sarmalayıcı, serileştirici sınıfının uygulanmasını kolaylaştırır. Bu durumda, seri durumdan çıkarıcı girişi her zaman web URL'nizden bir dizedir.
public abstract class DeepLinkSerializer<T : Any> : KSerializer<T>DeepLinkSerializer uygulamak için üç yöntem yazarsınız.
Öncelikle bir dizi adı seçersiniz.
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
override val serialName: String
get() = "Bitmap"
...
}Ad alanı oluşturulması gerekmez ancak Kotlin serileştirmenin, serileştiricinizi ne zaman kullanacağını bilecek kadar benzersiz olması gerekir.
Ardından, web URL'nizden bu dizeyi alan ve tür güvenli nesnenizi oluşturmaktan sorumlu olan deserialize işlevini uygulamanız gerekir.
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
override val serialName: String
get() = "Bitmap"
override fun deserialize(value: String): Bitmap {
val imageBytes = Base64.decode(value, Base64.DEFAULT)
// This better be a small image…
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}
...
}Son olarak, tam tersi yaklaşım olan serialize yöntemini uygulayacaksınız. Bu yöntem, aynı anahtar sınıfınız rememberNavBackStack gibi durumlarda kullanılıyorsa tercih edilir.
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
...
override fun serialize(value: Bitmap): String {
val byteArrayOutputStream = ByteArrayOutputStream()
// Compress the bitmap into the output stream (e.g., JPEG or PNG)
value.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
val byteArray = byteArrayOutputStream.toByteArray()
// Encode the byte array to a Base64 string
return Base64.encodeToString(byteArray, Base64.DEFAULT)
}
}Şimdi özel serileştiricinizi, biraz özel olan bu sınıfa sahip özelliğe uygulayacaksınız. Web UriDeepLinkMatcher, gerisini halledecek.
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
...
}
@Serializable data class Session(
val id: String,
@Serializable(BitmapSerializer::class)
val placeholderImage: Bitmap? = null
) : NavKeyAncak bu Base64 dizesindeki veya web URL'nize eklenen JSON'daki verilerin yalnızca küçük bir kısmını istiyorsanız ne yapmanız gerekir?
Bu durumda, web URL'nizin bir kısmını Kotlin Serialization'a ulaşmadan önce önceden işlemek mantıklı olabilir.
Bu durumları yönetmek için UriDeepLinkMatcher sınıfını genişletebilir ve matchArguments yöntemini geçersiz kılabilirsiniz.
JSON blob'umuz için yalnızca oturum kimliğini istiyor olabiliriz. Diğer verilerle ilgilenmiyoruz.
Bu nedenle alt sınıfımızı oluşturup matchArguments yöntemini geçersiz kılıyoruz.
class SessionUriDeepLinkMatcher(
uriPattern: DeepLinkUri
) : UriDeepLinkMatcher(uriPattern, serializer=serializer<Session>()) {
override fun matchArguments(
pathArgs: Map<String, List<String>>,
queryArgs: Map<String, List<String>>,
fragmentArgs: Map<String, List<String>>
): UriMatchResult<Session>? {
...
}Girişler, URI yolunuzdan eklenen tüm yer tutucular olan ham dizelerdir.
Bu sayede, super.matchArguments hizmetini çağırmadan önce bu değerleri önceden işleyip düzenleyebiliriz.
class SessionUriDeepLinkMatcher(
uriPattern: DeepLinkUri
) : UriDeepLinkMatcher(uriPattern, serializer=serializer<Session>()) {
override fun matchArguments(
pathArgs: Map<String, List<String>>,
queryArgs: Map<String, List<String>>,
fragmentArgs: Map<String, List<String>>
): UriMatchResult<Session>? {
val finalQueryArgs = buildMap {
putAll(queryArgs)
val jsonBlob = remove("session")?.firstOrNull()
if (jsonBlob != null) { put("session", extractSessionIdFromJson(jsonBlob)) }
}
return super.matchArguments(pathArgs, finalQueryArgs, fragmentArgs)
}Burada basitleştirilmiş değerler kullanabiliriz ve Kotlin serileştirmesiyle uğraşmamız gerekmez.
Teşekkürler, Ian.
Bu teknikleri sık sık kullanmanız gerekmeyeceğini umuyoruz ancak eşleştirme davranışını nasıl kontrol edebileceğiniz konusunda çok düşündük.
Anladım.
Derin bağlantı eşleştiricilerle nasıl NavKey elde edeceğimizi öğrendik. Ancak yalnızca gezinme anahtarı içeren bir eski yığın, en iyi kullanıcı deneyimini sunmayabilir.
Bu nedenle, geri düğmesinin doğru şekilde çalışması için yapay bir eski yığın kullanmanız gerekir.
Sentetik kısmı, eski yığının kullanıcının hiçbir zaman manuel olarak gitmediği anahtarları içermesinden kaynaklanır ancak manuel olarak gitmiş olsaydı izleyeceği yol budur.
Örneğin, başlamak üzere olan bir oturumla ilgili bildirimi tıklayan kullanıcıyı doğrudan o oturuma yönlendirirsiniz. Ancak kullanıcı sistem geri düğmesini tetiklediğinde, başlatıcıya çıkmadan önce ana ekranınıza dönmesini istersiniz.
Bu HomeScreen, yapay eski yığının bir parçası olur.
Derin bağlantı API'leri için iki rakip gereksinimi dengelemek istedik.
Bir yandan, farklı derin bağlantıların veya farklı eşleştiricilerin, sentetik eski yığınlarını oluşturmak için farklı mantıklar kullanmak isteyeceğini biliyoruz.
Örneğin, her derin bağlantıdan önce bir HomeScreen eklemek, bir ayar ekranındaki alt ekrana derin bağlantı oluşturmak isteyene kadar işe yarayabilir.
Şimdi, bu derin bağlantı için yapay eski yığınınıza ek bir katman eklemeniz gerekir.
Doğal olarak bu, eski yığın oluşturucuyu her derin bağlantı eşleştiriciye bağlar.
Ancak doğru ekranın gösterilmesine kadar geçen her milisaniyenin size gerçek para kaybettirdiğini de biliyoruz.
Bu nedenle her maç sonucu için yapay bir eski yığın hesaplamak istemedik.
Sadece en iyi eşleşmeyi istedik.
Her eşleştirici için özel mantık istiyor ancak her eşleşme için istekli bir şekilde sentetik eski yığın oluşturmak istemiyorsak ne yapmalıyız?
Birkaç başarısız denemeden sonra her iki gereksinimi de dengeleyen bir yaklaşım bulduk.
Hangi eşleştiriciyi kullanırsanız kullanın, verilen MatchResult ile eski yığını geç oluşturmak için bir withBackStack çağırarak bu eşleştiriciyi genişletebilirsiniz.
val homeMatcher = UriDeepLinkMatcher(...).withBackStack { matchResult ->
listOf(Home, matchResult.key)
}
val sessionMatcher = UriDeepLinkMatcher(...).withBackStack { matchResult ->
listOf(Home, SessionList, matchResult.key)
}
val backStackMatchers: List<BackStackMatcher<NavKey, NavKey>> = listOf(
homeMatcher, sessionMatcher
)
val matchResults = backStackMatchers.mapNotNull { matcher.match(request) }
val bestMatch: BackStackMatchResult<NavKey, NavKey>? = matchResults.maxOrNull()
val backStack = bestMatch?.backStack ?: listOf(Home)Yani birden fazla eşleştiricimiz varsa her birinin, belirli bir NavKey için uygun eski yığını oluşturmak üzere kendi withBackStack çağrısı olur.
Çağırdığınız her eşleştirici withBackStack kullanırsa en temiz kodu elde edersiniz. Bu, size eski yığın eşleştiricilerin bir listesini verir.
En iyi eşleşmeyi bulduğunuzda, elde ettiğiniz eşleşme sonucu BackStackMatchResult olur.
Bu, MatchResult sınıfının bir alt sınıfıdır ve withBackStack lambda'nız tarafından oluşturulan tüm eski yığın olan backStack adlı yeni bir özellik ekler.
Bu katmanlı yaklaşım, eşleştiricilerin içindeki mantığı withBackStack lambda'nızda kullanmak istediğiniz özel mantıktan ayırmamıza olanak tanıdı.
Bu kısa bir konuşma olduğu için maalesef süremizin sonuna geldik. Ancak Navigation 3'te bulunan ve Navigation 3 tariflerimizde bulabileceğiniz diğer özelliklerden birkaçını inceleyebiliriz.
Anladım.
Bu nedenle, eşleştirme mantığını kısa devre yapmak ve eşleştiriciyi işleme, MIME türüne veya kendi özel filtrelerinize göre hızlı bir şekilde hariç tutmak için herhangi bir eşleştiriciye filtre ekleyebilirsiniz.
Bu işlem, URI ayrıştırma veya özel serileştirme (Kotlin Serileştirme) işlemlerinden önce gerçekleşir.
Ayrıca, NavKey değerinizi almak için herhangi bir ayrıştırma yapmanız gerekmeyen ancak yine de filtre uygulamak istediğiniz durumlar için özel olarak tasarlanmış bir StaticKeyDeepLinkMatcher da sunuyoruz.
@Serializable object ImagesKey: NavKey
// custom filter
val mimeTypeFilter = DeepLinkMatcher.mimeTypeFilter("image/jpg")
val matcher = StaticKeyDeepLinkMatcher(ImagesKey, listOf(mimeTypeFilter))Bu, JPEG resimleri açabilen bir fotoğraf düzenleme uygulaması gibi yalnızca işlem veya MIME türü derin bağlantıları işlemek için idealdir.
İşlem ve MIME türünden bahsetmişken, DeepLinkRequest sınıfınıza herhangi bir özel RequestExtras ekleyebilirsiniz. Özel filtreleriniz ve DeepLinkRequest alt sınıflarınız, mantıklarının bir parçası olarak bunu kullanabilir.
Bu sayede A/B testi çerçevelerini veya harici sinyalleri dahil edebilirsiniz.
Ayrıca ekstralar için de tür güvenli API'lerimiz var.
val extras = requestExtras {
put(SectionExtrasKey, "A")
}Bu mantığın neredeyse tamamı Nav3'ün ortak KMP kodunda yer alır. Bu sayede, tüm eşleştiricileriniz ve eski yığın mantığınız platformlar arasında paylaşılabilir.
Navigation 3'ün derin bağlantıları nasıl desteklediğini daha ayrıntılı bir şekilde incelemek isterseniz Nav3 tariflerimize göz atabilirsiniz.
Evet. Ayrıca derin bağlantı API'lerimizi DI tariflerimize ve NowInAndroid adlı örnek uygulamamıza entegre ettik.
Kısa konuşmamıza katıldığınız için teşekkür ederiz. İyi yolculuklar dileriz.
Teşekkür ederiz.
val matcher = UriDeepLinkMatcher(
...,
...
)val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri(...),
...
)val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/home"),
...
)// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
…
)// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
serializer = serializer<Session>()
)// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("http://ads.android.com/session/{id}"),
serializer = serializer<Session>()
)// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("http://ads.android.com/session/{id}"),
serializer = serializer<Session>()
)
val request = DeepLinkRequest(
uri = DeepLinkUri("http://ads.android.com/session/abcd")
)
val matchResult: UriMatchResult<Session>? = matcher.match(request)
val backStack = listOf(matchResult?.key ?: Home)val matchers = listOf(...)
// gets all non-null MatchResult
val matchResults = matchers.mapNotNull { matcher ->
matcher.match(request)
}val matchers = listOf(...)
// gets all non-null MatchResult
val matchResults = matchers.mapNotNull { matcher ->
matcher.match(request)
}
// compare matches
val backStack = listOf(
matchResults.maxOrNull()?.key ?: Home
)// Feature A Module
@ContributesTo(ActivityScope::class)
@BindingContainer
object FeatureAModule {
@IntoSet
@Provides
fun provideDeepLinkMatcher() : UriDeepLinkMatcher<NavKey> =
UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/featureA"),
serializer<FeatureBKey>(),
)
}
// in MainActivity
@Inject lateinit var matchers: Set<UriDeepLinkMatcher<NavKey>>// pattern = "http://ads.android.com/session/{id}"
@Serializable data class Session(val id: Int) : NavKey// pattern = "http://ads.android.com/session/{id}#referrer={referrer}
@Serializable data class Session(
val id: Int,
val referrer: String? = null,
) : NavKey// "id={id}&base64={placeholderImage}"
@Serializable data class Session(
val id: String,
val placeholderImage: Bitmap? = null
) : NavKey
// "jsonBlob={user}"
@Serializable data class Profile(
val user: User,
) : NavKeyobject BitmapSerializer : DeepLinkSerializer<Bitmap>() {
...
}public abstract class DeepLinkSerializer<T : Any> : KSerializer<T>object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
override val serialName: String
get() = "Bitmap"
...
}object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
override val serialName: String
get() = "Bitmap"
override fun deserialize(value: String): Bitmap {
val imageBytes = Base64.decode(value, Base64.DEFAULT)
// This better be a small image…
return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
}
...
}object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
...
override fun serialize(value: Bitmap): String {
val byteArrayOutputStream = ByteArrayOutputStream()
// Compress the bitmap into the output stream (e.g., JPEG or PNG)
value.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
val byteArray = byteArrayOutputStream.toByteArray()
// Encode the byte array to a Base64 string
return Base64.encodeToString(byteArray, Base64.DEFAULT)
}
}object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
...
}
@Serializable data class Session(
val id: String,
@Serializable(BitmapSerializer::class)
val placeholderImage: Bitmap? = null
) : NavKeyclass SessionUriDeepLinkMatcher(
uriPattern: DeepLinkUri
) : UriDeepLinkMatcher(uriPattern, serializer=serializer<Session>()) {
override fun matchArguments(
pathArgs: Map<String, List<String>>,
queryArgs: Map<String, List<String>>,
fragmentArgs: Map<String, List<String>>
): UriMatchResult<Session>? {
...
}class SessionUriDeepLinkMatcher(
uriPattern: DeepLinkUri
) : UriDeepLinkMatcher(uriPattern, serializer=serializer<Session>()) {
override fun matchArguments(
pathArgs: Map<String, List<String>>,
queryArgs: Map<String, List<String>>,
fragmentArgs: Map<String, List<String>>
): UriMatchResult<Session>? {
val finalQueryArgs = buildMap {
putAll(queryArgs)
val jsonBlob = remove("session")?.firstOrNull()
if (jsonBlob != null) { put("session", extractSessionIdFromJson(jsonBlob)) }
}
return super.matchArguments(pathArgs, finalQueryArgs, fragmentArgs)
}val homeMatcher = UriDeepLinkMatcher(...).withBackStack { matchResult ->
listOf(Home, matchResult.key)
}
val sessionMatcher = UriDeepLinkMatcher(...).withBackStack { matchResult ->
listOf(Home, SessionList, matchResult.key)
}
val backStackMatchers: List<BackStackMatcher<NavKey, NavKey>> = listOf(
homeMatcher, sessionMatcher
)
val matchResults = backStackMatchers.mapNotNull { matcher.match(request) }
val bestMatch: BackStackMatchResult<NavKey, NavKey>? = matchResults.maxOrNull()
val backStack = bestMatch?.backStack ?: listOf(Home)@Serializable object ImagesKey: NavKey
// custom filter
val mimeTypeFilter = DeepLinkMatcher.mimeTypeFilter("image/jpg")
val matcher = StaticKeyDeepLinkMatcher(ImagesKey, listOf(mimeTypeFilter))val extras = requestExtras {
put(SectionExtrasKey, "A")
}