Ali Shobeyri
Ali Shobeyri
خواندن ۵ دقیقه·۳ سال پیش

ایجاد یک سیستم Navigation در Multi Module Application

Where should we navigate?
Where should we navigate?

یکی از چالش های برنامه نویسی در اندروید زمانی هست که شما به صورت مالتی ماژول برنامه نویسی می‌کنید، در این حالت ممکن هست سر ایجاد یک سیستم Navigation به مشکل بر بخورید، چند مقاله در این مورد دیدم که مثلا در یکی از اون ها کلا بیخیال Navigation Component شده بودند و یک سیستم جدید برای خودشون درست کردند (که من نمی‌خواستم این کار رو بکنم)، بعضی هاشون کل سیستم نویگیشن رو به لایه App برده بودن (که به نظرم خیلی درست نیست) و بعضی هم کارهای دیگه . . .

در این مقاله با ایجاد یک ماژول جدا برای Navigation قراره یک سیستم برای این داستان درست کنیم .

توجه : من در حال درست کردن یک پروژه سمپل برای خودم هستم و این مشکل رو برخوردم، این مقاله بیشتر حالت ترجمه از این مقاله داره که هم کمی تغییرش دادم و یک سری چیزا بهش اضافه کردم

فرض کنید پروژه شما به این صورت باشه :

ما قراره از Search به Detail بریم و همین طور می‌تونیم از Saved هم به اون قسمت بریم، همچنین میشه بین Search و Saved با استفاده از نویگیشن باتن ها سوئیچ انجام داد

فلوی برنامه
فلوی برنامه

خب ما اینجا چند مورد رو باید هندل کنیم، یکی اتصال Navigation Button ها به Navigation Component یکی هم هندل Navigate بین ماژول های مختلف .

اول از همه که باید library های مورد نظر رو اضافه کنیم که نیاز به توضیح نداره و می‌تونید از این لینک استفاده کنید.

بعد از اون باید ببینیم معماری رو به چه صورت بچینیم، خب ما یک MainActivity رد لایه App خواهیم داشت که شامل FragmentContainerView و BottomNavigation هست :

https://gist.github.com/sasssass/734e6d60f470f8f5b1c11a20adc36b0e

اگر دقت کنید یک فایل نویگیشن گراف به اسم app_navigation به FragmentContainer الحاق شده، خب این کجاست ؟ این فایل و یک سری فایل مربوط به Navigation System در یک ماژول به نام Navigator (یا هر اسم دیگه که دوست دارید) وجود دارند :

یک گراف اصلی که app_navigation هست و یه گراف فرعی و یک nav_id که شامل یک سری id برای نویگیشن هستند، هدف این هست که یک سری nested graph در app_navigation داشته باشیم و هندل نویگیت بین ماژولی را با اون ها انجام بدیم، اول از همه nav_id رو ببینیم :

https://gist.github.com/sasssass/2902f6cb97e17716e25f9933435c20dc

این Id ها از این به بعد در قسمت های مختلف استفاده خواهند شد و معرف nested graph های ما هستند، حالا فایل app_navigation رو با هم ببینیم :

https://gist.github.com/sasssass/f92466c92ad449b42bd7db698e14b077

که شمای گرافیکی اون به این صورت هست :


خب یک سری توضیحات باید بدم، همون طور که می‌بینید ما یک سری nested_graph در این فایل ایجاد کردیم که home رو هم saved_nav گذاشتیم، یک global action هم تعریف کردیم که کاربر از هرجا بخواهد بتواند به Detail برود (اکشن های گلوبال یعنی متکی به فرگمنت نیستند، از هر جا بخواهیم برویم به هر صفحه مورد نظر، می‌تونید این لینک رو ببینید)، تا اینجا چیز خاصی وجود نداشته، وقتشه بریم ببینیم این saved_nav یا detail_nav یا ... چی هستند، برای نمونه saved_nav :

https://gist.github.com/sasssass/65a1579c3212b3423d0e4ddbdf0cca02

خب این که خالیه :/ ولی مشکلی نداره ما عین همین فایل رو در ماژول Saved هم داریم که به این صورته :

https://gist.github.com/sasssass/ffda4f62983d859000954025836e3082

الان چرا ما دو تا فایل تعریف کردیم ؟ و اینکه یکی اصلا چرا خالی بود، چند تا نکته اینجا هست، یکی اینکه ماژول Navigator متکی به هیچ ماژولی نیست ولی باقی ماژول ها متکی به اون هستند یعنی در داخل فایل gradle باید implementation رو انجام بدید :

implementation project(":navigator")

یک نکته رو در فهم این قضیه باید متوجه بشید و اونم اینه :

نکته اینه که ما در هیچ کدوم از این قسمت ها از + استفاده نکردیم، چرا ؟ چون همه این آیدی ها به همون فایل nav_id که ساخته بودیم اشاره دارن و دارن به این طریق با هم لینک می‌شن، یعنی درسته که ما یک فایل نویگیشن خالی در ماژول Navigator ساختیم ولی به مشکلی بر نمیخوریم چون در Run-Time این فایل به فایل اصلی که در داخل خود ماژول مثلا Saved هست به خاطر وجود Id یکسان لینک می‌شن !

یک خلاصه از کارایی که کردیم :

  1. اومدیم در Activity مون یک FragmentContainer اضافه کردیم
  2. یک ماژول Navigator درست کردیم که توش فایل های مربوط به نویگیشن مثل آیدی ها و nested graph ها و app_navigation بود
  3. در هر feature ای که داریم فایل نویگیشن مربوط به اون feature رو ایجاد می‌کنیم

خب بریم سراغ بقیه چیزا، در ماژول Navigator اول از همه این Interface رو اضافه می‌کنیم :

https://gist.github.com/sasssass/6f3cb2c390bd455e6eceddb8bf774b35

این interface در MainActivity قرار داده میشه (Implement میشه) که یکم بعد بررسی می‌کنیم این رو، داخل این Interface یک NavigationFlow ورودی قرار داده شده :

https://gist.github.com/sasssass/1458f43c386865b919257716f1b02882

این یک Sealed Class هست که متریال لازم برای هندل نویگت بین ماژولی رو درش قرار می‌دیم، یعنی مثلا اگه خواستیم بریم به صفحه Detail لازمه به Interface مون DetailFlow رو پاس بدیم .

نکته : به نظرم این فایل شاید کمی اصول SOLID رو نقض کنه، در واقع اصل O یعنی Open-Closed، چرا ؟ چون ما به ازای هر فیچر جدید شاید مجبوریم بشیم این فایل رو تغییر بدیم که درست نیست، می‌تونیم به جاش از وراثت استفاده کنیم یا ... .

یک فایل دیگر هم به اسم Navigator داریم که با استفاده از Sealed Class مون میاییم و مشخص می‌کنیم کدوم اکشن رو از نویگیشن گرافمون استفاده کنه :

https://gist.github.com/sasssass/bd03b7e4607b9882689be46b5026e818

در این فایل مثلا اگر DetailFlow بیاد میاد و اون اکشن گلوبالی که تو app_navigation بوده رو استفاده می‌کنه و در این مثال خاص دیتا هم داشته که به صورت bundle بشه پاس داده میشه .

خب حالا وقت استفاده از این فایل هاست که در MainActivity انجام میشه (ماژول App هم مثل Feature ها باید متکی به ماژول Navigator باشه و این ماژول در gradle اش اد بشه) :

https://gist.github.com/sasssass/5eb79aed8dfe9d8ef62f437b0eb05160

خب خیلی هم خوب اما این تابع navigateToFlow چطور صدا زده بشه؟ اینجا تابع باید از سمت Fragment ها صدا زده بشن، برای اینکار می‌تویند یک BaseFragment ایجاد کنید که همه Fragment ها از اون ارث ببرن :

https://gist.github.com/sasssass/0ad3ef6693d3b78e19465ccc4495ea35

این طوری ما دسترسی به اون Interface رو در BaseFragment داریم، الان دیگه همه چیز حله، اگر بخوام می‌تونم مثلا از ماژول Saved به Detail برم :

https://gist.github.com/sasssass/bfbf85d98ba2726de75d7ab975d04287

و تمام ! فقط یک نکته می‌مونه و اون اتصال Navigation Button ها به Navigation Component هست، خب همون طور که می‌دونید Navigation Button ها یک فایل menu رو می‌گیرن :

https://gist.github.com/sasssass/112f2614fc8ca241df3653feded15adc

آیدی که برای آیتم ها داده میشه باید مجددا همون آیدی باشه که در ماژول Navigator قرار دادیم، حالا یک تابع هست به این شکل :

bottomNav.setupWithNavController(navController)

https://gist.github.com/sasssass/27af0f155d1b9d7bf119e33ffb8ce344

و بعدش خود Navigation Componentبقیه شو انجام می‌ده !!!


آدرس کانال تلگرامی ما : لینک

من رو در لینکدین ، اینستاگرام و یوتیوب دنبال کنید !!!

اگه دوست داشتید می‌تونید به صفحه Spotify و SoundCloud بنده هم برید و موسیقی های منو گوش بدید !!!

اندرویدبرنامه نویسیکاتلینandroidkotlin
برنامه نویس اندروید - https://www.linkedin.com/in/iryebohs/
شاید از این پست‌ها خوشتان بیاید