تنظیم ماژول های اندروید با Dagger 2 — به زبان ساده
امروزه با توجه به روند توسعه اپلیکیشنهای مدرن، دورهای که همه کدهای اندروید درون یک ماژول حجیم منفرد در اپلیکیشن جای میگرفت به سر آمده و عصر توسعه ماژولار آغاز شده است. ماژولار بودن نیازمند ملاحظات دقیق در مورد معماری و توسعه مقیاسپذیر است. اگر میخواهیم کار خود را با پروژهای که قابلیت ماژولهسازی دارد آغاز کنیم، میتوانیم تزریق وابستگی را از طریق Dagger 2 اجرا کنیم. در این مقاله روش تنظیم ماژولهای اندروید با Dagger 2 را از طریق یک مثال ساده مورد بررسی قرار میدهیم.
فرضیات پروژه
شیوه تنظیم پروژه ساده ما به شرح زیر است:
- سه اکتیویتی وجود دارد که MainActivity میتواند FeatureOneActivity یا FeatureTwoActivity را اجرا کند.
- هر کدام از اکتیویتیها تزریق وابستگی خاص خود را دارند.
هر کدام از وابستگیها با یک شبکه و ریپازیتوری سینگلتون تزریق میشوند یعنی یک کپی به همه وابستگیها تزریق میشود.
ماژولبندی پروژه
برای حفظ مقیاسپذیری در بلند مدت فرض میکنیم که ماژولبندی بر اساس اکتیویتی صورت میگیرد. آن را به روشهای دیگر نیز میتوان انجام داد، اما برای سادهتر بودن مثال از این روش استفاده شده است. بدین ترتیب موارد زیر را داریم:
- یک ماژول اپلیکیشن که شامل MainActivity است.
- یک ماژول «قابلیت یکم» که شامل FeatureOneActivity است.
- یک ماژول «قابلیت دوم» که شامل FeatureTwoActivity است.
- و در آخر ماژول مبنا است که همه اشیای کلاس سینگلتون باید در آن تزریق شوند.
چالش تنظیم Dagger 2
برای تنظیم Dagger 2 به طور معمول از لینکهای سادهای مانند زیر استفاده میشود:
در اینجا با یک چالش برای توسعه ماژولار اندروید مواجه میشویم. وابستگیهای مدوری بین کامپوننت اشیای هدف پدید میآید که امکان جداسازی ماژولها را از بین میبرد. به بیان دقیقتر، شیء هدف به طور معمول یک وابستگی لوکال دارد که باید از طریق کامپوننت فرعی مختص تست و لوکال تزریق شود.
در نمودار فوق، میبینیم که همین مشکل رخ داده است، یعنی یک وابستگی مدور غیرمستقیم بین شیء هدف، کامپوننت و کامپوننت فرعی مختص هدف تشکیل یافته است. اگر این پیوند را حفظ کنیم، هیچ روشی برای پشتیبانی از Dagger 2 در ماژولهای متفاوت وجود نخواهد داشت.
Dagger 2 برای اندروید ماژولار
برای حل چالش فوق باید استفاده از Dagger 2 تک گرافی مرسوم را کنار بگذاریم:
1DaggerMyComponent.create()
برای اندروید ماژولار گرافهای چندگانه Dagger 2 را ساخته و به هم متصل میکنیم.
ماژول پایه
در ماژول پایه همه وابستگیهای مورد نیاز از سوی همه ماژولهای دیگر را نگهداری میکنیم. بدین ترتیب یک کامپوننت منفرد به نام BaseComponent ایجاد میکنیم. یک سینگلتون ایجاد میکنیم تا مطمئن شویl که کپی یکسانی از همه وابستگیها به هر چیزی که مورد نیاز است تزریق میشود.
1@Singleton
2@Component(modules = [BaseNetworkModule::class, BaseRepositoryModule::class])
3interface BaseComponent {
4 val baseNetwork: BaseNetwork
5 val baseRepository: BaseRepository
6}
کد فوق BaseComponent را نشان میدهد. توجه کنید که baseNetwork و baseRepository را صراحتاً افشا کردهایم و لذا میتوانند از سوی گراف دیگر Dagger 2 در ادامه مورد دسترسی قرار گیرند. این BaseComponent در BaseApplication به اپلیکیشن ما اختصاص مییابد.
1class BaseApplication: Application() {
2 companion object {
3 val baseComponent by lazy {
4 DaggerBaseComponent.create()
5 }
6 }
7}
به این ترتیب اینک ماژول وابستگیها را در اختیار داریم.
ماژولهای قابلیت (Feature)
هرکدام از ماژولهای قابلیت (شامل ماژول app) گراف خاص Dagger 2 خود را تولید میکنند. تولید گراف به baseComponent وابسته است.
هیچ پیوند مدوری خارج از ماژول وجود ندارد و از این رو baseComponent میتواند به سادگی در ماژول دیگری قرار گیرد.
1@ActivityScope
2@Component(dependencies = [BaseComponent::class], modules = [FeatureDependentModule::class])
3interface FeatureComponent {
4 fun inject(featureOneActivity: FeatureOneActivity)
5}
در کد فوق پیوندی از FeatureComponent به BaseComponent میبینیم. توجه کنید که ActivityScope را معرفی کردهایم، زیرا باید یک «کامپوننت دامنهدار» (scoped component) داشته باشیم که به کامپوننت دامنهدار دیگر وابسته باشد. یعنی BaseComponent دارای دامنه سینگلتون است. در FeatureActivity گراف مورد نظر را به وسیله دسترسی به BaseComponent و عرضه آن به FeatureComponent-اش تولید خواهیم کرد.
1DaggerFeatureComponent.builder()
2 .baseComponent(BaseApplication.baseComponent)
3 .build()
4 .inject(this)
اینک ماژول قابلیت خودش گراف تولید شده خودش را دارد و آن قدر انعطاف دارد که هر معماری Dagger مورد نیاز از قبیل راهاندازی کامپوننتهای فرعی لازم را اجرا کند.
جمعبندی تنظیمات
اگر همه ماژولها را مانند توصیف فوق به هم متصل کنیم، مانند نمودار زیر خواهد شد. هر ماژول، مولد گراف Dagger 2 خاص خود را دارد و آنها به BaseComponent ماژول پایه وابسته هستند.
این تنظیمات موجب ارتقای انعطافپذیری معماری Dagger 2 درون ماژول میشود. همچنین آن را میتوان به شکل دیگری از افراز ماژول که متفاوت از مثال فوق است بسط داد. کد نمونه آن را در این لینک (+) میتوانید ببینید.
سخن پایانی
این مقاله برای تنظیم یک پروژه جدید مناسب است، چون میتوانید همه وابستگیهای اصلی را برنامهریزی کرده و آنها را در کلاس مبنا قرار دهید و با این حال اگر هم اینک نیز یک پروژه عظیم دارید که اغلب (نه همه) کد در ماژول app قرار دارد، میتوانید از این روش بهره بگیرید.
در صورتی که فکر میکنید Dagger بیش از حد پیچیده است و نمیتوانید بر آن مسلط شوید و میخواهید از رویکرد مشابهی بهره بگیرید، پیشنهاد میکنیم از Koin (+) استفاده کنید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی اندروید
- مجموعه آموزشهای برنامهنویسی
- آموزش برنامه نویسی اندروید (Android) – مقدماتی
- روش های اجرای اپلیکیشن های اندروید در ویندوز — راهنمای کاربردی
- Dagger 2 در اندروید (بخش اول) — راهنمای پیشرفته
==