پیادهسازی الگوی “تک اکتیوتی، چند فرگمنت”
توی پست قبلی در مورد الگوهای مختلف ساخت پروژههای اندروید صحبت کردم و خلاصهای از تحقیقاتمو نوشتم. خروجی اون تحقیقات یه پروژه تمرینی براساس الگوی “تک اکتیویتی، چند فرگمنت” شده که در ادامه بیشتر در موردش توضیح میدم و میتونید از طریق گیتهابم به سورسش دسترسی پیدا کنید.
توی این پروژه تمرکزم روی این قضیه بوده که چطوری جا به جایی بین صفحههای برنامه (همون فرگمنتها) رو مدیریت کنم. برای همین فعلا الگوهای معماری مثل MVP یا Dagger2 و RxJava رو قاطی پروژه نکردم. در ضمن از زبان Kotlin برای ساخت پروژه استفاده کردم تا با این زبان بیشتر آشنا بشم ولی بدیهی هس که هرکاری توی این پروژه کردم رو میشه خیلی راحت توی یه پروژهی جاوایی انجام داد.
چالشها
سعی کردم انواع چالشهایی که بخاطر استفاده از فرگمنتها توی پروژههای واقعی پیش میاد رو توی این پروژه پیاده کنم تا اگر پیادهسازیم مشکلی داره سریعتر متوجه بشم. چالشهایی که به ذهنم رسید این موارد بوده: (اگر چالش دیگهای به ذهنتون میرسه بهم بگید تا در موردش بیشتر صحبت کنیم)
۱- استفاده از BottomNavigationView: جدیدا در بیشتر برنامهها از Bottom Navigation استفاده میشه، به همین دلیل منم برای شروع این مدل نویگیشن رو انتخاب کردم. البته در آینده میشه مثال رو با navigation drawer هم پیاده کرد یا مدیریت backstack رو از حالتی که الان هست به حالتی شبیه اینستاگرام تغییر داد که هر تب برای خودش backstack جدا داشته باشه.
۲- مدیریت دکمهی Back: بصورت پیشفرض این دکمه رو اکتیویتی مدیریت میکنه ولی خیلی وقتا پیش میاد بعضی از فرگمنتها نیاز دارند که خودشون اینکارو به جای اکتیویتی میزبان مدیریت کنند.
۳- استفاده از Tab به همراه ViewPager در فرگمنت: گاهی پیش میاد یه فرگمنت داخلش تعدادی تب داره که هر تب خودش فرگمنت جدایی هس، توی این حالت مدیریت فرگمنتهای داخلی به عهدهی اداپتر ViewPager هست
۴- استفاده از فرگمنتی که داخلش تعدادی فرگمنت دیگه داره: در ظاهر میتونه شبیه حالت قبلی باشه ولی فرقش توی اینه مدیریت فرگمنتهای داخلی با فرگمنت والد هست.
۵- مدیریت ویوهای داخل اکتیویتی: گاهی توی اپلیکیشن نیاز هست وقتی کاربر روی آیتمی کلیک کرد، به جزییاتش بره. توی این حالت دیگه نیاز نیست Bottom Navigation رو ببینه یا شاید لازم باشه دکمهی FAB نشون داده بشه.
ایدهها
برای اینکه بتونم این چالشهای بالارو حل کنم، از ایدههای زیر استفاده کردم.
ایده اول – مدیریت جا به جایی صفحات یا همون navigation رو به یه کلاس به اسم NavigationManager سپردم. البته خیلی از مثالهای توی گیتهاب اینجوری بودند. اینکار باعث رعایت شدن اصل Single Responsibility که حرف S در اصول SOLID هست میشه. این کلاس برای مدیریت navigation نیاز به یدونه FragmentManager و شناسه لیوتی که قراره کانتینر فرگمنتها باشه داره. وقتی مدیریت navigation در یک کلاس جدا باشه، در آینده میشه مثلا بدون اینکه بقیه بخشهای برنامه رو خیلی تغییر داد، بجای backstack اندروید از یه backstack کاستوم استفاده کرد. درکل دست آدمو برای توسعههای بعدی باز میذاره.
ایده دوم- برای اینکه بتونم ساختار تو در تو بودن فرگمنتهارو در جاهایی که خود فرگمنت شامل تعدادی فرگمنت دیگه باشه رو مدیریت کنم، از پیادهسازی Dagger2 ایده گرفتم. ایده اینه هر کلاسی که اینترفیس HasNavigationManager رو پیادهسازی کرده باشه وظیفه داشته باشه فرگمنتهای داخلشو مدیریت کنه ( از ماژول اندروید Dagger2 و نحوهی کمک گرفتنش اینترفیسهای HasActivityInjector یا HasFragmentInjector الگو گرفتم). قضیه اینه هر فرگمنت وقتی داره لود میشه چک میکنه چه کسی مدیریتشو داره و ازش یه object از نوع NavigationManager میگیره تا کارهایی که نیاز داره رو انجام بده. اون کلاسی که اینترفیس HasNavigationManager رو پیاده میکنه، باید در پیادهسازی متد provideNavigationManager یه object از نوع NavigationManager برای فرگمنتهای که مدیریت میکنه فراهم کنه تا اونا به روشی که گفتم ازش استفاده کنند. اینکار باعث میشه چالش ۴ راحتتر حل بشه و مدیریت سلسله مراتبی باشه، در نتیجه اکتیویتی میزبان همهی این فرگمنتها خیلی شلوغ نشه!
ایده سوم- فرگمنتهایی که نیاز داشته باشند دکمهی back رو مدیریت کنند، میتونند متد onBackPressed رو که داخل BaseFragment هست، override کنند. اکتیوتی وقتی دکمهی بک زده میشه، چک میکنه فرگمنت جاری (من فرض کردم همیشه یه فرگمنت به عنوان فرگمنت جاری باشه! این فرض رو میشه در آینده عوض کرد. الان هر فرگمنتی که آخرین بار نشون داده شده باشه میشه فرگمنت جاری) میخواد بک رو هندل کنه یا نه؟! اگر کرد که اکتیویتی کاری نمیکنه و اگر نکرد، خود اکتیویتی کار همیشگیش رو انجام میده.
ایده چهارم- برای اینکه هر فرگمنت بتونه المانهای داخل اکتیویتی رو برای خودش مدیریت کنه، یه سری متد توی اینترفیس FragmentInteractionListener تعریف کردم. فرگمنتها برای ارتباط با اکتیویتی میزبان از اینترفیسهای خودشون مثل OnNotificationFragmentInteractionListener یا OnProfileFragmentInteractionListener استفاده میکنند که همشون از FragmentInteractionListener ارثبری میکنند، در نتیجه همه فرگمنتها میتونن متدهای داخل اینترفیس FragmentInteractionListener که اکتیویتی میزبانشون پیادهسازی کرده رو صدا بزنند.
در آخر تاکید کنم این پروژه تمرینی در حال توسعه هست و امکان داره جاهاییش اشکال داشته باشه یا حتی در آینده تغییر بکنه، در نتیجه با اینکه تلاشم اینه همهی BestPracticeهارو داخلش رعایت کنم ولی فعلا زوده که خیلی خیلی مطمئن از ساختارش توی پروژههاتون استفاده کنید.
میتونید سورس کامل پروژه رو توی گیتهابم و ریپوی SingleActivityPattern ببینید.