علیرضا نظری
علیرضا نظری
خواندن ۶ دقیقه·۳ سال پیش

در نسخه اندروید بلوبانک چگونه دیپ‌لینک ها را پیاده سازی کردیم؟ (Conditional DeepLink)

زمین بازی را عوض کردیم؟؟ =))
زمین بازی را عوض کردیم؟؟ =))


قبل از اینکه دیپ‌لینک رو بررسی کنیم باهم یه دور ساختار لینک و URL رو بررسی کنیم با این تفاوت که خیلی ساده بگم به protocol ما scheme و به domain ما host هم میگیم:

ساختار لینک یا URL
ساختار لینک یا URL


اما در اندروید راه های مختلفی برای پیاده سازی دیپ‌لینک هست, ساده ترین نوع اون تعریف intent-filter در manifest پروژه تا حالت سینه به سینه و طی کردن فلو برای رفتن به یک صفحه مشخص وجود داره:

<intent-filter android:label=&quotBluBank&quot android:autoVerify=&quottrue&quot> <action android:name=&quotandroid.intent.action.VIEW&quot /> <category android:name=&quotandroid.intent.category.DEFAULT&quot /> <category android:name=&quotandroid.intent.category.BROWSABLE&quot /> <data android:scheme=&quothttps&quot android:host=&quotapplication.blubank.com&quot /> ... </intent-filter>


اما با اومدن نویگیشن کامپوننت کار ما برای ایجاد دیپ‌لینک بسیار راحت شده و اگر پروژه تون لاگین یا ثبت‌نام نداشته باشه به راحتی با چند خط کد می‌تونید انواع دیپ‌لینک رو پیاده سازی و لذت ببرید :دی


<fragment> <deepLink android:id=&quot@+id/deepLink&quot app:uri=&quotblubank://application/goto?page=card/update&quot /> </fragment>


اگر هم نیاز به دریافت ورودی تو صفحه مقصد دارید به راحتی با تعریف argument می‌تونید داینامیک یکسری دیتا رو بگیرید و در uri دیپ‌لینک مشخص کنید کدوم query یا path اون آرگیومنت رو پر میکنه:


<fragment> <argument android:name=&quotnumber&quot app:argType=&quotstring&quot /> <deepLink android:id=&quot@+id/chargeDeepLink&quot app:uri=&quotblubank://application/goto?page=pay/charge&phone={number}&quot /> </fragment>


همین چند خط برای خیلی از پروژه ها کافیه و به همین سادگی و به همین خوشمزگی می‌تونید بین هزارن (:دی) صفحه از پروژه‌تون با دیپ‌لینک جابجا بشید.

اما اگر پروژه بانکی کار کنید که امنیت حرف اول رو میزنه و همینطور پروژه تون نیاز دارید اول کاربر ثبت نام و لاگین کنه اونوقت دیگه به همین سادگی نیست.

تو پروژه آیگپ که نویگیشن کامپوننت وجود نداشت ما بصورت سینه به سینه انتقال میدادیم و اگر تو یه فلویی شرایطی برقرار نبود اجازه ادامه نمی‌دادیم. (سورس کدش موجوده اگر حوصله خوندن و دیدن کد کثیف داشتید حتما تو گیتهاب سرچ کنید ببینید :دی)

تو پروژه X که اکتیویتی بیس بود ما به صفحه مقصد میزدیم و اگر به ۴۰۱ میخوردیم صفحه لاگین رو بالا میاوردیم و بعد برگشت و ادامه فعالیت.

اما تو بلوبانک ما نه خواستیم سینه به سینه جلو بریم و فلو رو طی کنیم و نه ریکویست الکی بزنیم و ۴۰۱ بخوریم و الکی بار اضافه کنیم.
نویگیشن کامپوننت اینجا به هیچ عنوان جواب نمیده و اصلا فکری براش نکردن و پس برنامه‌نویسان غیور ایرانی دست به شگفتی زدند و این سد را با یک چشم (یک و نیم ماه پس از عمل چشم) و در نیم اسپرینت شکستند و حماسه ای خلق کردند. (بچه بیا پایین سرمون درد گرفت :)) )

در همین بین که به نویگیشن کامپوننت فحش میدادم, یک مقاله ای از دوستان بانک آلمانی خوندیم که اساتید نتونسته بودن هندل کنند و لاگین رو جدا و اکتیویتی کرده بودند و از سینگل اکتیویتی خارج شدند. دیپ‌لینک ها رو به اکتیویتی لاگین میگرفتن و به بعد اجازه هندل میدادن. (ایران 1 - آلمان 0)

خلاصه که حماسه ای خلق کردیم و الان هر دیپ‌لینکی رو آخ نگفتیم و با یکسری تریک به هدف مون رسیدیم. کد ها شاید در نگاه اول ایده‌آل بنظر نیان اما جوری توسعه داده شده تا زمانی که نویگیشن کامپوننت ساپورت کنه (که بعید میدونم) ما با کمترین تغییرات به کارمون ادامه بدیم و ریفکتور کنیم.


چند مورد که برای پیاده سازی Conditional Deep Link بایستی رعایت کنیم:


نویگیشن کامپوننت ابزاری برای مدیریت دستی دیپ‌لینک ها نداره و اگر دیپ‌لینکی تعریف کنید توش بدون اجازه و بدون اعتنا به اینکه کدش رو تو manifest ردید یا نه اجرا و به مقصد می‌بره.
خب برای این مورد ما دیپ لینک های برنامه رو که در منی‌فست تعریف کردیم رو در گراف با همون scheme تعریف نمی‌کنیم.

مثلا اگر با لینک http://google.com بیاد رو به یک scheme من در آوردی تبدیل میکنیم تا نویگیشن کامپوننت آنها رو خودکار هندل نکنه و خودمون این وسط بایستیم و Conditional Deep Link رو داشته باشیم:

http://google.com => deeplink://google.com blubank://application/goto?page=card/update => customized://application/goto?page=...

در این حالت ما در onCreate یا onNewIntent اکتیویتی مون دیپ لینک ها رو میگیریم و شرایط رو بررسی و اجازه جابجایی می‌دیم.
مثلا ما یک کلاس Navigator داریم و دیپ‌لینک های ورودی رو به اون میدیم, کلاس شرایط لاگین بودن یا نبودن, فرگمنت لاگین در back stack هست یا نه, دیپ لینک نیاز به لاگین دارد یا نه و.. رو بررسی و تصمیم به ادامه دادن میگیره یا پیغام مناسب رو نشون میده.

با این روش نه سینگل اکتیویتی رو نقض کردیم نه ریکویست اضافه زدیم و نه سینه به سینه رفتیم. فقط تو نقطه شروع یک بررسی کردیم و بعد اجازه رفتن مستقیم به صفحه مورد نظر و یا اول لاگین و بعد رفتن به صفحه مورد نظر رو دادیم و به هدفمون رسیدیم.

پس حالت دیپ لینک در گراف مربوطه این شکلی شد:

<deepLink android:id=&quot@+id/deepLink&quot app:uri=&quotcustomized://application/goto?page=card/updatw&quot />

این رو هم عنوان کنم که با ادرسی که تو گراف تعریف کردید کسی از بیرون نمی‌تونه وارد شه بحاطر اینکه تو منیفست ست نشده و با اصلی میان داخل و طبق کد و تصمیم شما ادامه میدن.

حالا فقط چالش ها همینه و مورد اضافه‌ای نیست؟ چرا هست

با این روش شما میتونید هر کاری بکنید. اول اینکه ساختار دیپ‌لینک ها رو نقض نکنید و با کلاس Navigatorتون میتونید این وسط خیلی دیپ لینک ها رو نرمالایز normalize کنید.

مثلا قبل از پیاده سازی دیپ لینک ما دو صفحه رو یکی در نظر گرفته بودیم و با یه boolean استیت صفحه رو مشخص میکردیم اما دیپ لینک دو مدل بود و جدا در نظر گرفته بود.
من یا باید میومدم ریفکتور میکردم چند تا صفحه رو یا حماسه جدید خلق میکردم. که یا علی گفتم و حماسه ای به پا کردم دوووبارره, یاعلی بگو خدا هوامونو داااره (بچههه بیا پایین باز سرمون درد گرفت)

فرگمنت من این شکلی بود:

<fragment> <argument android:name=&quotisCharge&quot app:argType=&quotboolean&quot /> </fragment>

که برای دیپ‌لینک این صفحه یک query param به ته لینک اضافه کردم تا بتونم دو حالتی رو چک کنم و اسمش رو مثلا additional-data گذاشتم تا تو کل دیپ‌لینک ها یک دست باشه:

<fragment> <argument android:name=&quotisCharge&quot app:argType=&quotboolean&quot /> <deepLink android:id=&quot@+id/chargeDeepLink&quot app:uri=&quotxx://app/goto?page=pay/charge-list&add-data={isCharge}&quot /> <deepLink android:id=&quot@+id/internetDeepLink&quot app:uri=&quotxx://app/goto?page=pay/internet-list&add-data={isCharge}&quot /> </fragment>

و تو کلاس نویگیتور نوشتم:

fun Uri.normalizeExternalDeepLinks(): Uri { val newDeepLink = this.buildUpon() when (val page = this.getQueryParameter(&quotpage&quot)) { CHARGE_DEEP_LINK, INTERNET_DEEP_LINK -> { val isCharge = newDeepLink.toString().contains(CHARGE_DEEP_LINK) newDeepLink.appendQueryParameter(&quotadd-data&quot, isCharge.toString()) } } return newDeepLink.build() }


این هم یک چالشی بود که حل شد و یسری چالش های دیگر مثل گرفتن دیپ لینک در صفحه مقصد که نویگیشن کامپوننت ابزاری برای این نداره که تو صفحه مقصد بفهمید چه دیپ‌لینکی اومده و نفهم کامله و برای اون تا این لحظه آبان ۱۴۰۰ چیزی اضافه نکرده و ترجیح میدم خودتون بهش بر بخورید و نسبت به توضیحات بالا تریکی بزنید و اگر نشد better call Saul چیزه کال علیرضا :دی

یسری چالش دیگه هم بود که خدایی دستم درد کرد اینقدر نوشتم و اگر شد در آینده ورژن دو این مقاله رو می‌نویسم.

امیدوارم تونسته باشم منظور رو رسونده باشم و براتون مفید باشه و شوخی های تو متن هم صرفا جهت قابل تحمل کردن مقاله بوده.


علیرضا نظری

اندرویدکاتلینبلوبانک
Android Team Lead at blu Bank
شاید از این پست‌ها خوشتان بیاید