सॉफ़्टवेयर इंजीनियर
मॉड्यूलर Android ऐप्लिकेशन में डीप लिंक मैनेज करने से, लिंक काम करना बंद कर सकते हैं और रनटाइम के दौरान गड़बड़ियां हो सकती हैं. इस सेशन में, Compose के लिए Jetpack Navigation3 के नए टाइप-सेफ़ डीप लिंकिंग आर्किटेक्चर के बारे में बताया गया है. इससे आपको एक मज़बूत और स्केलेबल रूटिंग सिस्टम बनाने में मदद मिलेगी. हम यह जानेंगे कि आउट-ऑफ़-द-बॉक्स डीप लिंक मैचिंग एपीआई को कैसे लागू किया जाए. साथ ही, जटिल टाइप-सेफ़ आर्ग्युमेंट के लिए कस्टम सीरियलाइज़र लिखने और डाइनैमिक हैरारकी वाली पिछली गतिविधियां बनाने का तरीका भी जानेंगे. इससे उपयोगकर्ता के नेविगेशन को अनुमानित बनाए रखने में मदद मिलती है.
DeepLinkRequest क्लास, इनपुट को एनकैप्सुलेट करती है. इसमें Android इंटेंट कंस्ट्रक्टर भी शामिल है.UriDeepLinkMatcher, यूआरआई पैटर्न को मैच करने और आर्ग्युमेंट को टाइप-सेफ़ NavKey प्रॉपर्टी में डीसीरियलाइज़ करने के लिए, Kotlin Serialization का इस्तेमाल करता है. प्रिमिटिव टाइप अपने-आप काम करते हैं. अगर वैल्यू को पार्स नहीं किया जा सकता, तो मैच नहीं होते.DeepLinkSerializer को बढ़ा सकते हैं या matchArguments को बदल सकते हैं. UriDeepLinkMatcher की सबक्लासिंग की मदद से, Kotlin सीरियलाइज़ेशन के चलने से पहले, रॉ यूआरआई प्लेसहोल्डर को प्रीप्रोसेस किया जा सकता है.withBackStack को कॉल करने से, सबसे सही मैच के लिए सिंथेटिक बैक स्टैक लेज़ी तरीके से बनते हैं. इससे यह पक्का होता है कि सिस्टम का बैक बटन सही तरीके से काम करे. साथ ही, हर मान्य मैच के लिए, पिछली गतिविधियों को कंप्यूट करने की ज़रूरत नहीं पड़ती.ट्रांसक्रिप्ट
नमस्ते, मेरा नाम इयान लेक है. मैं Jetpack आर्किटेक्चर टीम का इंजीनियरिंग मैनेजर हूं.
मैं क्लैरा फ़ोक हूँ. मैं Nav 3 की टेक लीड हूँ.
आज हम आपको एक ऐसी सुविधा के बारे में बताएंगे जो मुझे बहुत पसंद है. यह सुविधा है, डीप लिंक.
मुझे ये दोनों ही बहुत पसंद हैं. इसलिए, मैंने Nav3 के 1.2 रिलीज़ के हिस्से के तौर पर, Nav2 और Nav3, दोनों को लागू किया.
मुझे लगता है कि आपने ऐसा तीन बार किया है, क्योंकि हमने Nav 3 रेसिपी में भी इसे लागू किया था.
हां, यह सही है.
हमने एपीआई में कई बदलाव किए, ताकि यह Nav3 के Compose-फ़र्स्ट तरीके के साथ आसानी से काम कर सके.
इसकी शुरुआत, रेसिपी पर हमारा फ़ोकस बढ़ने से हुई.
हां, Navigation 3 का हमारा एक मुख्य लक्ष्य यह था कि हम आपके काम में रुकावट न डालें.
अगर आपको कोई ऐसी सुविधा बनानी है जो काफ़ी अहम है, तो उसे बनाने के लिए आपको हमसे सुविधा का अनुरोध करने की ज़रूरत नहीं है.
इसलिए, जब हमसे यह सवाल पूछा जाता है कि "ठीक है, मैं यह कैसे करूं?"
या "मैं इस समस्या को कैसे हल करूं?"
शुरुआत से ही कस्टम एपीआई बनाने के बजाय, हम एक रेसिपी बनाते हैं. यह एक गाइड होती है, जिसमें बताया जाता है कि Navigation 3 में कोई बदलाव किए बिना, अपने ऐप्लिकेशन में उस सुविधा को कैसे जोड़ा जा सकता है.
सिर्फ़ बताने के बजाय, करके दिखाने के इसी तरीके की वजह से, हमने GitHub पर Nav3 रेसिपी रिपॉज़िटरी बनाई.
अब इसमें, इस्तेमाल के सामान्य उदाहरणों के लिए 35 से ज़्यादा रेसिपी मौजूद हैं. जैसे, कई बैक स्टैक और बॉटम शीट. इनसे आपको वह सारा कोड मिलता है जो आपको अपने ऐप्लिकेशन में लागू करने के लिए चाहिए.
इसका मतलब है कि नवंबर 2025 में Nav3 के 1.0 रिलीज़ के लिए भी, हमारे पास डीप लिंक लागू करने का तरीका पहले से ही मौजूद था. ऐसा इसलिए, क्योंकि हमें पता था कि यह आप में से कई लोगों के लिए ज़रूरी है.
हालांकि, जब हमें पता चलता है कि किसी रेसिपी के लिए, हेल्पर मेथड के एक बंडल की ज़रूरत है, जिसे आपको अपने ऐप्लिकेशन में कॉपी करके चिपकाना होगा, तब हम ग्रेजुएशन नाम की प्रोसेस पर विचार करते हैं. इसमें हम उन हेल्पर को लेते हैं और कहते हैं, "ठीक है, अगर वे नेविगेशन थ्री का हिस्सा होते, तो वे असल में कैसे दिखते?"
इससे यह पक्का होता है कि जिन डेवलपर ने पहले ही उस कोड को कॉपी-पेस्ट कर लिया है उन्हें कोई समस्या न हो.
इस कोड को सेव करके रखा जा सकता है.
अगर आपके पास कोई कस्टम समाधान है और वह काम कर रहा है, तो यह बहुत अच्छी बात है.
आपके पास कोई फ़ोर्स्ड माइग्रेशन नहीं है.
हालांकि, अगर आपको कोई नई समस्या आ रही है या आपकी कोई नई ज़रूरत है, तो आपको एक आसान और जांचा-परखा समाधान मिलता है. इससे आपको सीधे तौर पर नेविगेशन के हिस्से के तौर पर उसका इस्तेमाल करने की सुविधा मिलती है.
हालांकि, मेरे लिए सबसे अच्छी बात यह है कि हेल्पर जोड़ने के बाद, हम वापस जाकर अपनी रेसिपी अपडेट करते हैं और कोड मिटाने से हमें काफ़ी खुशी मिलती है.
इस तरह, जो लोग उस समस्या के लिए सुझाए गए तरीके की रेसिपी देख रहे हैं उनके पास सबसे अप-टू-डेट समाधान होता है.
ज़ाहिर है, बाद में चीज़ें ज़्यादा साफ़ तौर पर समझ आती हैं. ग्रैजुएशन प्रोसेस से हमें रेसिपी में बनाए गए हेल्पर को ध्यान से देखने का मौका मिलता है. इससे हम यह तय कर पाते हैं कि "क्या ये वाकई इसके लिए सबसे सही एपीआई हैं?"
इस मामले में, हमें उन चीज़ों को बेहतर बनाने का मौका मिलता है, क्योंकि हम उन्हें लाइब्रेरी में ले जाते हैं.
डीप लिंक के मामले में भी ऐसा ही था.
धन्यवाद, इयान.
इसलिए, सिद्धांत के तौर पर, रेसिपी और हमारे फ़ाइनल एपीआई, दोनों काफ़ी मिलते-जुलते हैं. इसकी वजह यह है कि Nav 3 में, पिछली गतिविधियों का ऐक्सेस आपके पास होता है.
इसका मतलब है कि हमारे डीप लिंक एपीआई का फ़ोकस यह पता लगाना है कि शुरुआती पिछली गतिविधियां क्या होनी चाहिए. खास तौर पर, जब उपयोगकर्ता आपके ऐप्लिकेशन में जाने के लिए किसी डीप लिंक पर क्लिक करते हैं, तो उन्हें कौनसी स्क्रीन दिखती है.
हालांकि, जब उपयोगकर्ता उस वेब यूआरआई पर क्लिक करता है, तो असल में क्या होता है?
अब Android फ़्रेमवर्क के लिए ज़रूरी है कि आप अपने ऐप्लिकेशन में काम करने वाले हर डीप लिंक के लिए, इंटेंट फ़िल्टर का एलान करें. भले ही, Nav3 का इस्तेमाल किया जा रहा हो या नहीं.
अब दिलचस्प बात यह है कि जब वह डीप लिंक आपकी ऐक्टिविटी को शुरू करने के लिए ट्रिगर करता है, तब क्या होता है.
यहां इनपुट, वह इंटेंट है जिससे आपका ऐप्लिकेशन शुरू होता है. साथ ही, वह गतिविधि है जो शुरू हुई है.
हालांकि, डीप लिंक कई तरह के हो सकते हैं.
उदाहरण के लिए, हो सकता है कि आपको किसी टेस्ट के लिए इसे मैन्युअल तरीके से बनाना हो या Kotlin Multiplatform की सुविधा का फ़ायदा लेना हो.
इसलिए, हमने डीप लिंक इनपुट को केएमपी के साथ काम करने वाली DeepLinkRequest क्लास में शामिल किया है.
Android पर, हमारे पास एक DeepLinkRequest कंस्ट्रक्टर है, जो Android इंटेंट लेता है.
इसके बाद, हमारा लक्ष्य यह पता लगाना है कि किसी अनुरोध के आधार पर, हमें कैसे पता चलेगा कि आपकी पिछली गतिविधियों में क्या NavKey डालना है?
यहीं पर, हमारी DeepLinkMatcher क्लास काम आती है.
यह एक अनुरोध लेता है और MatchResult को आउटपुट करता है. इसमें मैच से जुड़ा NavKey शामिल होता है.
खास तौर पर वेब यूआरआई के लिए, हमारे पास एक UriDeepLinkMatcher है. इसमें किसी अनुरोध को MatchResult में बदलने के लिए ज़रूरी सभी लॉजिक शामिल होते हैं.
val matcher = UriDeepLinkMatcher(
...,
...
)अब, UriDeepLinkMatcher का पहला पैरामीटर, यूआरआई पैटर्न होता है.
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri(...),
...
)मान लें कि आपको कोई ऐप्लिकेशन बनाना है और उसकी होम स्क्रीन पर डीप लिंक जोड़ना है.
यहां पैटर्न एक सटीक यूआरआई है. हम एचटीटीपी और एचटीटीपीएस, दोनों से अपने-आप मैच करने के लिए, एचटीटीपी स्कीमा को छोड़ सकते हैं.
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/home"),
...
)हालांकि, अगर हमें किसी खास ADS सेशन को डीप लिंक करना है, तो उस डीप लिंक में वह जानकारी शामिल होती है जिसे हमें निकालना होता है. जैसे, सेशन आईडी.
// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
…
)इसलिए, इस मामले में हमारा पैटर्न, कर्ली ब्रैकेट का इस्तेमाल करता है. ये ब्रैकेट, उस जानकारी के लिए प्लेसहोल्डर के तौर पर काम करते हैं जिसे हमें डीप लिंक से निकालना है.
किसी भी तरह के इनपुट को क्लास इंस्टेंस में बदलने के लिए, पहले से ही एक आसान लाइब्रेरी मौजूद है. इसे Kotlin सीरियलाइज़ेशन कहते हैं.
इसलिए, हमारा UriDeepLinkMatcher, Kotlin डिकोडर का इस्तेमाल करके, इनपुट यूआरआई को डीसीरियलाइज़ करता है और NavKey इंस्टेंस को आउटपुट करता है.
// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
serializer = serializer<Session>()
)अब डिकोडर को यह जानना होगा कि उसे किस क्लास के साथ काम करना है. इसलिए, आपको मैचर में अपने NavKey के लिए एक सीरियलाइज़र भी पास करना होगा.
// target NavKey
@Serializable data class Session(val id: String) : NavKey
val matcher = UriDeepLinkMatcher(
uriPattern = DeepLinkUri("http://ads.android.com/session/{id}"),
serializer = serializer<Session>()
)डिकोडर, प्लेसहोल्डर को कुंजी की प्रॉपर्टी से सही तरीके से मैच कर सके, इसके लिए प्लेसहोल्डर का नाम, प्रॉपर्टी के नाम से मैच करना चाहिए.
इसलिए, इस उदाहरण में प्रॉपर्टी का नाम आईडी है, जो सेशन के आईडी पैरामीटर से मैच करता है.
ठीक है, अब हमारे पास UriDeepLinkMatcher है. इसलिए, हमें आने वाले डीप लिंक के साथ DeepLinkRequest बनाना होगा.
// 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)इसके बाद, मैच फ़ंक्शन को कॉल करके और हमारे अनुरोध को पास करके, मैचिंग की जाती है.
इससे आपको एक नलेबल MatchResult मिलेगा, जिसमें NavKey शामिल होगा. हमारे उदाहरण में, यह सेशन के लिए कुंजी होगी. अगर नतीजा शून्य है, तो हम डिफ़ॉल्ट रूप से होम स्क्रीन पर जा सकते हैं.
अब कई डीप लिंक को कई मैचर के तौर पर लिखा जाना चाहिए: हर डीप लिंक के लिए एक मैचर.
उदाहरण के लिए, यहां हमारे पास सेशन के लिए कुंजी के लिए दो यूआरआई पैटर्न हैं: एक लेगसी न्यूज़लेटर पैटर्न के लिए और एक सामान्य वेब यूआरआई के लिए.
हमारे पास ADS की होम स्क्रीन का डीप लिंक भी है. इसलिए, हम इसके लिए तीसरा मैचर जोड़ते हैं.
साथ ही, सभी मैचर को उसी सूची में जोड़ा जाएगा जिससे हम बार-बार गुज़रते हैं.
इसलिए, अब हमारा मैच, हमारी सूची में मौजूद सभी मैचर के लिए एक लूप बन जाता है. इसमें हम हर मैचर को कॉल करते हैं, ताकि अनुरोध से मैच करने वाले सभी मैचर मिल सकें.
इसके लिए, हम mapNotNull का इस्तेमाल करके, मैच न होने वाले डेटा को फ़िल्टर कर सकते हैं. साथ ही, सिर्फ़ मान्य मैच की सूची वापस पा सकते हैं.
val matchers = listOf(...)
// gets all non-null MatchResult
val matchResults = matchers.mapNotNull { matcher ->
matcher.match(request)
}अब ज़्यादातर मामलों में, आपकी सूची में सिर्फ़ एक नॉन-नल मैच होगा. हालांकि, कुछ मामलों में एक से ज़्यादा मान्य मैच हो सकते हैं.
उदाहरण के लिए, मान लें कि हम एक खास कीनोट स्क्रीन पेश करते हैं, जो उसी सेशन यूआरआई पैटर्न का फिर से इस्तेमाल करती है. हालांकि, अब आईडी प्लेसहोल्डर के बजाय, इसमें एक स्टैटिक कीनोट पाथ है.
इसलिए, इस मामले में एक ही अनुरोध, दोनों मैचर से मैच हो सकता है: सेशन के लिए कुंजी, जहां आईडी, keynote के बराबर है और keynote NavKey.
MatchResult, Comparable को लागू करता है. इसलिए, हम max फ़ंक्शन का इस्तेमाल करके, सूची में मौजूद सभी मान्य मैच की तुलना करके सबसे सही मैच ढूंढ सकते हैं. इसके अलावा, maxOrNull का इस्तेमाल करके, उस मामले को मैनेज किया जा सकता है जहां अनुरोध से मैच करने वाला कोई डीप लिंक नहीं था.
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>>इस तरीके का इस्तेमाल करके, हमने अपनी रेसिपी रिपॉज़िटरी में मौजूद Hilt, Metro, और Koin रेसिपी को अपडेट किया है, ताकि उनमें डीप लिंक शामिल किए जा सकें.
क्लारा ने बताया कि हमारा UriDeepLinkMatcher, डीप लिंक के अनुरोध से फ़ील्ड निकालने के लिए, Kotlin Serialization का इस्तेमाल करता है. साथ ही, इसे हमारी टाइप-सेफ़ क्लास में बदल देता है. जैसे, हमने जो सेशन क्लास दिखाई है.
जैसा कि यहां लिखा है, यह काफ़ी आसान ट्रांसफ़ॉर्मेशन है.
यूआरआई एक स्ट्रिंग है.
फ़ील्ड एक स्ट्रिंग है.
वाह, यह तो बहुत आसान था.
हालांकि, हमें पता था कि हमें सिर्फ़ स्ट्रिंग प्रॉपर्टी से आगे बढ़ना होगा.
Kotlin Serialization, हमें पहले से ही एक मज़बूत फ़ाउंडेशन देता है, ताकि हम इस पर काम कर सकें.
बूलियन, पूर्णांक, फ़्लोट, और एनम जैसे सभी प्रिमिटिव टाइप, अपने-आप काम करते हैं. अगर डीप लिंक की वैल्यू को उस खास टाइप के तौर पर पार्स नहीं किया जा सकता, तो मैच अपने-आप फ़ेल हो जाता है और वैल्यू के तौर पर शून्य दिखता है.
उदाहरण के लिए, अगर हमारे सेशन का आईडी पैरामीटर असल में स्ट्रिंग के बजाय पूर्णांक है, तो ऐसी स्ट्रिंग पास करने पर मैच नहीं होगा जिसे पूर्णांक में पार्स नहीं किया जा सकता.
// pattern = "http://ads.android.com/session/{id}"
@Serializable data class Session(val id: Int) : NavKeyइसी तरह, अगर आपको वैकल्पिक आर्ग्युमेंट चाहिए, जैसे कि आपके वेब यूआरएल से जुड़े Analytics डेटा के लिए, तो वे आपकी कुंजी पर ऐसी प्रॉपर्टी हो सकती हैं जिनकी डिफ़ॉल्ट वैल्यू होती हैं.
// 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,
) : NavKeyऐसा हो सकता है कि आपके डीप लिंक में Base64 कोड में बदली गई इमेज अटैच हो.
जैसे, हो सकता है कि उन्होंने एक बड़ा JSON ब्लॉब बनाया हो और तय किया हो कि "चलो, इसे आपके ऐप्लिकेशन के हर यूआरएल में डाल देते हैं."
अब आपके पास एक और तरीका है. भले ही, आपको दिए गए वेब यूआरएल को बदला न जा सके, लेकिन आपको उससे कुछ जानकारी निकालनी हो.
इसलिए, पहली तकनीक यह है कि हम अपनी DeepLinkSerializer क्लास को एक्सटेंड करें, ताकि यह कंट्रोल किया जा सके कि उस आर्ग्युमेंट को स्ट्रिंग से आपकी टाइप-सेफ़ प्रॉपर्टी में कैसे डीसीरियलाइज़ या ट्रांसफ़ॉर्म किया जाए.
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
...
}DeepLinkSerializer, Kotlin Serialization के लिए एक हेल्पर रैपर है. इससे Kotlin Serialization की सीरियलाइज़र क्लास को लागू करना आसान हो जाता है. ऐसा इसलिए होता है, क्योंकि इस मामले में डीसीरियलाइज़र इनपुट हमेशा आपके वेब यूआरएल की स्ट्रिंग होती है.
public abstract class DeepLinkSerializer<T : Any> : KSerializer<T>अब DeepLinkSerializer को लागू करने के लिए, तीन तरीके लिखें.
सबसे पहले, सीरियल का नाम चुनें.
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
override val serialName: String
get() = "Bitmap"
...
}इसे नेमस्पेस करने की ज़रूरत नहीं है. हालांकि, यह इतना यूनीक होना चाहिए कि Kotlin सीरियलाइज़ेशन को पता चल सके कि आपके सीरियलाइज़र का इस्तेमाल कब करना है.
इसके बाद, आपको डीसीरियलाइज़ लागू करना होगा. इसे आपके वेब यूआरएल से स्ट्रिंग पास की जाती है. साथ ही, यह आपके टाइप-सेफ़ ऑब्जेक्ट को बनाने के लिए ज़िम्मेदार होता है.
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)
}
...
}आखिर में, आपको सीरियलाइज़ लागू करना होगा. यह डीसीरियलाइज़ करने का उल्टा तरीका है. इसका इस्तेमाल तब किया जाता है, जब आपकी एक ही कुंजी क्लास का इस्तेमाल rememberNavBackStack मामलों में किया जाता है.
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)
}
}अब आपको अपने कस्टम सीरियलाइज़र को उस प्रॉपर्टी पर लागू करना होगा जिसमें वह क्लास है जो थोड़ी खास है. बाकी काम वेब UriDeepLinkMatcher करेगा.
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
...
}
@Serializable data class Session(
val id: String,
@Serializable(BitmapSerializer::class)
val placeholderImage: Bitmap? = null
) : NavKeyलेकिन क्या होगा अगर आपको उस base 64 स्ट्रिंग या आपके वेब यूआरएल से अटैच किए गए JSON से सिर्फ़ थोड़ा सा डेटा चाहिए?
ऐसे में, Kotlin सीरियलाइज़ेशन का इस्तेमाल करने से पहले, अपने कुछ वेब यूआरएल को प्री-प्रोसेस करना सही रहेगा.
ऐसे मामलों को मैनेज करने के लिए, UriDeepLinkMatcher को बड़ा किया जा सकता है और इसके matchArguments तरीके को बदला जा सकता है.
अब, हो सकता है कि हमारे JSON ब्लॉब के लिए, हमें सिर्फ़ सेशन आईडी की ज़रूरत हो. जैसे, हमें बाकी चीज़ों की ज़रूरत न हो.
इसलिए, हम अपनी सब-क्लास बनाते हैं और matchArguments को ओवरराइड करते हैं.
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>? {
...
}इनपुट, वे रॉ स्ट्रिंग होती हैं जो आपके यूआरआई पाथ से जोड़े गए सभी प्लेसहोल्डर होते हैं.
इससे हमें super.matchArguments को कॉल करने से पहले, उन वैल्यू को प्रोसेस करने और उनमें बदलाव करने की सुविधा मिलती है.
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)
}अब हमारे पास यहां आसान वैल्यू हो सकती हैं. साथ ही, हमें Kotlin सीरियलाइज़ेशन के साथ छेड़छाड़ करने की ज़रूरत नहीं है.
धन्यवाद, इयान.
उम्मीद है कि आपको इन तकनीकों का इस्तेमाल अक्सर नहीं करना पड़ेगा. हालांकि, हमने इस बारे में काफ़ी सोचा है कि मैचिंग के व्यवहार को कैसे कंट्रोल किया जा सकता है.
ठीक है।
अब हमें पता है कि डीप लिंक मैचर की मदद से NavKey कैसे पाएं. हालांकि, सिर्फ़ नेविगेशन बटन वाली पिछली गतिविधियां, उपयोगकर्ता को बेहतर अनुभव नहीं दे सकतीं.
इसलिए, बैक बटन के सही तरीके से काम करने के लिए, आपको सिंथेटिक बैक स्टैक की ज़रूरत होगी.
सिंथेटिक पार्ट का मतलब है कि पिछली गतिविधियां में ऐसी कुंजियां होती हैं जिन पर उपयोगकर्ता ने कभी मैन्युअल तरीके से नेविगेट नहीं किया. हालांकि, अगर वह मैन्युअल तरीके से नेविगेट करता, तो वह इसी पाथ का इस्तेमाल करता.
उदाहरण के लिए, शुरू होने वाले किसी सेशन की सूचना पर क्लिक करने से, उपयोगकर्ता सीधे उस सेशन पर पहुंच जाएगा. हालांकि, जब वह सिस्टम बैक को ट्रिगर करेगा, तो हो सकता है कि आप उसे लॉन्चर से बाहर निकलने से पहले, होम स्क्रीन पर वापस ले जाना चाहें.
वह होम स्क्रीन, सिंथेटिक पिछली गतिविधियों का हिस्सा बन जाती है.
डीप लिंक एपीआई के लिए, हमें दो अलग-अलग ज़रूरतों को पूरा करना था.
एक तरफ़, हम जानते हैं कि अलग-अलग डीप लिंक या अलग-अलग मैचर, अपनी पिछली गतिविधियों को सिंथेटिक तरीके से बनाने के लिए अलग-अलग लॉजिक का इस्तेमाल करना चाहेंगे.
उदाहरण के लिए, हर डीप लिंक से पहले होम स्क्रीन जोड़ने से, तब तक काम बन सकता है, जब तक आपको सेटिंग स्क्रीन के अंदर मौजूद किसी सब-स्क्रीन पर डीप लिंक न करना हो.
अब आपको उस डीप लिंक के लिए, सिंथेटिक बैक स्टैक में एक अतिरिक्त लेयर की ज़रूरत होगी.
ज़ाहिर है कि इससे बैक स्टैक बिल्डर, हर डीप लिंक मैचर से जुड़ जाता है.
हालांकि, हम यह भी जानते हैं कि सही स्क्रीन दिखने में लगने वाले हर मिलीसेकंड की वजह से, आपको असल पैसे खर्च करने पड़ते हैं.
इसलिए, हम हर मैच के नतीजे के लिए, सिंथेटिक बैक स्टैक की गिनती नहीं करना चाहते थे.
हम सिर्फ़ सबसे सही मैच चाहते थे.
इसलिए, अगर हमें हर मैचर के लिए कस्टम लॉजिक चाहिए, लेकिन हम हर मैच के लिए सिंथेटिक पिछली गतिविधियां नहीं बनाना चाहते, तो हमें क्या करना चाहिए?
इसलिए, कुछ असफल कोशिशों के बाद, हमें एक ऐसा तरीका मिला जिससे दोनों ज़रूरतों को पूरा किया जा सकता है.
आपके पास किसी भी तरह का मैचर हो, उसे बेहतर बनाया जा सकता है. इसके लिए, withBackStack को कॉल करें, ताकि दिए गए MatchResult के साथ पिछली गतिविधियों को लेज़ी तरीके से बनाया जा सके.
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)इसलिए, अगर हमारे पास कई मैचर हैं, तो हर मैचर के पास उस खास NavKey के लिए सही पिछली गतिविधियां बनाने के लिए, अपना withBackStack कॉल होगा.
अगर कॉल किया गया हर मैचर withBackStack का इस्तेमाल करता है, तो आपको सबसे साफ़ कोड मिलेगा. इससे आपको पिछली गतिविधियों के मैचर की सूची मिलेगी.
सबसे ज़्यादा मिलते-जुलते नतीजे को BackStackMatchResult कहा जाता है.
यह MatchResult की सब-क्लास है. इसमें backStack नाम की एक नई प्रॉपर्टी जोड़ी जाती है. यह आपके withBackStack lambda से जनरेट की गई पूरी पिछली गतिविधियां होती हैं.
लेयरिंग के इस तरीके से, हम मैचर के अंदर मौजूद लॉजिक को आपके withBackStack लैम्डा में इस्तेमाल किए जाने वाले किसी भी कस्टम लॉजिक से अलग कर पाए.
यह एक लाइटनिंग टॉक है. इसलिए, माफ़ करें, हमारे पास समय कम है. हालांकि, हम नेविगेशन 3 में मौजूद कुछ अन्य सुविधाओं के बारे में बात कर सकते हैं. ये सुविधाएं आपको नेविगेशन 3 की रेसिपी में मिलेंगी.
ठीक है।
इसलिए, किसी भी मैचर में फ़िल्टर जोड़े जा सकते हैं, ताकि मैचिंग लॉजिक को शॉर्ट-सर्किट किया जा सके. साथ ही, कार्रवाई, एमआईएमई टाइप या अपने कस्टम फ़िल्टर के आधार पर मैचर को तुरंत बाहर रखा जा सके.
यह यूआरआई पार्सिंग या कस्टम सीरियलाइज़ेशन, Kotlin सीरियलाइज़ेशन से पहले होता है.
हम StaticKeyDeepLinkMatcher भी उपलब्ध कराते हैं. यह खास तौर पर उन मामलों के लिए है जहां आपको NavKey पाने के लिए, पार्स करने की ज़रूरत नहीं होती. हालांकि, आपको अब भी फ़िल्टर लागू करने होते हैं.
@Serializable object ImagesKey: NavKey
// custom filter
val mimeTypeFilter = DeepLinkMatcher.mimeTypeFilter("image/jpg")
val matcher = StaticKeyDeepLinkMatcher(ImagesKey, listOf(mimeTypeFilter))यह सिर्फ़ कार्रवाई या एमआईएमई टाइप वाले डीप लिंक को हैंडल करने के लिए सबसे सही है. जैसे, फ़ोटो में बदलाव करने वाला ऐसा ऐप्लिकेशन जो JPEG इमेज खोल सकता है.
ऐक्शन और एमआईएमई टाइप की बात करें, तो अपने DeepLinkRequest में कोई भी कस्टम RequestExtras अटैच किया जा सकता है. आपके कस्टम फ़िल्टर और DeepLinkRequest सबक्लास, इसे अपने लॉजिक के हिस्से के तौर पर इस्तेमाल कर सकते हैं.
इससे आपको एबी टेस्टिंग फ़्रेमवर्क या बाहरी सिग्नल को शामिल करने की सुविधा मिलती है.
साथ ही, हमारे पास अतिरिक्त सुविधाओं के लिए, टाइप-सेफ़ एपीआई भी हैं.
val extras = requestExtras {
put(SectionExtrasKey, "A")
}यह सारा लॉजिक, Nav3 के सामान्य केएमपी कोड में मौजूद है. इसलिए, आपके सभी मैचर और पिछली गतिविधियों का लॉजिक, सभी प्लैटफ़ॉर्म पर शेयर किया जा सकता है.
इसलिए, अगर आपको यह देखना है कि नेविगेशन 3, डीप लिंक के साथ कैसे काम करता है, तो Nav3 रेसिपी देखें.
हां, हमने अपने डीप लिंक एपीआई को डीआई रेसिपी और अपने हीरो ऐप्लिकेशन, NowInAndroid में भी इंटिग्रेट किया है.
हमारी लाइटनिंग टॉक में शामिल होने के लिए धन्यवाद. नेविगेट करने का आनंद लें.
धन्यवाद.
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")
}