در دو بخش قبل با DI و فریمورک Dagger آشنا شدید. هم چنین پروژهای که قراره روش کار کنیم رو از گیتهاب کلون کردید و با پکیج ها و فیچرهاش آشنا شدید. در این بخش قراره Dagger رو روی فیچر یا جریان Registration پیادهسازی کنیم.
دگر همهی کار رو با استفاده از انوتیشنها پیش می بره، به همین دلیل باید باهاشون آشنا بشیم. اولین انوتیشنی که باهاش آشنا میشیم اسمش Inject !
از ریفکتور کردن جریان رجیستر کار رو آغاز میکنیم. بارها گفتیم که دگر قراره برامون گراف برنامه رو بسازه که توش مشخصه کی به چی ربط داره. برای این که بتونه اینکارو بکنه باید بدونه از کلاسهایی که تو گراف هستند چطور نمونه بسازه؟
پس میاییم با یک انوتیشن @Inject
سازنده اون کلاسها رو علامت گذاری میکنیم. وقتی اینکارو میکنیم:
1- دگر می فهمه چطور از آن کلاس نمونه بسازه
2- اگر کلاس پارامتر ورودی داره، دگر میفهمه این کلاس مثلا آرگومان ایکس رو به عنوان وابستگی دریافت کرده.
کلاس RegistrationViewModel.kt
رو باز کنید. یادتونه که RegistrationActivity
این ویومدل رو می ساخت؟ واسه همین میخوایم طرز ساخت این ویومدل رو به دگر معرفی کنیم.
همون طور که می بینید در کاتلین حتما باید کی ورد constructor
رو قبل از انوتیشن @Inject
بیاریم. ویومدل کلاسUserManager
رو بعنوان وابستگی میگیره اما هنوز دگر خبر نداره چطور باید از روش نمونه بسازه و تو گراف بذاره، بهمین دلیل فایل UserManager
رو هم باز کنید و انوتیشن @Inject
رو برای سازنده کلاس بذارید.
حالا دیگه دگر میفهمه که چطور از روی این دو تا کلاس نمونه بسازه. همون طور که می بینیدUserManager
هم یک وابستگی داره که کلاس Storage
هست اما Storage
یک اینترفیسه و باید یه طور متفاوت به دگر یاد بدیم که چطور از روی اون نمونه بسازه. بعدا بهش می رسیم.
می دونید که اکتیوتیها و فرگمنتها توسط سیستم ساخته میشن و دگر نمی تونه از روشون نمونه بسازه. در اکتیویتی ها هرکدی که مربوط به مقدار دهی اولیه هست در متد on create قرار داره و ما نمی تونیم مثل قبل از از انوتیشین @Inject
در سازنده استفاده کنیم.پس چه کنیم؟
در فیلد اینجکشن که عموما در اکتیویتی و فرگمنت استفاده میشه، فیلدهایی که وابستگی هستن و میخواهیم از دگر درخواست کنیم اونارو برامون بسازه رو با انوتیشین @Inject
مشخص میکنیم. یادتونه که RegistrationActivity
قرار بود ویومدل رو بسازه. پس بیایید با فیلد اینجکشن به دگر بگیم اونو برامون بسازه و این وظیفه رو از روی دوش اکتیویتی برداریم.
برای رسیدن به این هدف دو تا کار می کنیم.
1- در اکتیویتی جایی که یک فیلد از جنس ویومدل ایجاد کرده رو با انوتیشن @Inject
مشخص میکنیم.
2- خطی که مربوط به ساخت ویومدل هست رو از متد on create حذف میکنیم.
وقتی از@Inject
در سازنده یک کلاس استفاده میکنیم به دگر اطلاع میدهیم چطور از روی کلاس نمونه بسازه و وقتی از@Inject
در یک فیلد استفاده میکنیم به دگر اطلاع می دهیم که چطور فیلد رو با ساخت یک نمونه از آن تایپ پر کنه.
بریم سراغ شناخت یک انوتیشن دیگه.
دگر قراره گراف نیازمندیهای پروژه رو بسازه و طوری اونو مدیریت کنه که ما بتونیم نیازمندیها رو از این گراف بگیریم.
برای رسیدن به این هدف یک اینترفیس می سازیم و با انوتیشن @Component
اون رو انوتیت یا حاشیه نویسی میکنیم. این کار باعث میشه دگر یک container بسازه همون کاری که در حالت دستی خودمون انجام میدادیم. این کلاس قراره یک سری متد داشته باشه که دگر قراره نیازمندی های پارامترهای این متدها رو تامین کنه.
این انوتیشن باعث میشه دگر کدهایی تولید کنه تا نیازمندی ها رو تامین یا پرواید کنه.
برای شروع یک متد به این اینترفیس اضافه میکنیم که به دگر بگه RegistrationActivity
درخواست تزریق یا اینجکشن داره.
در کنار پکیج registration
یک پکیج به نام di بسازید. درون این پکیج یک کاتلین فایل به نام ایجاد AppComponent.kt
و درون این فایل یک اینترفیس به همین نام بسازید. مسیر اینترفیس:
app/src/main/java/com/example/android/dagger/di/AppComponent.kt
با متدی که به اینترفیس اضافه کردیم به دگر میگیم که اکتیوتی درخواست اینجکشن داره و تو باید فیلدهای که با انوتیشن @Inject
در اکتیویتی مشخص شده رو براش پرواید کنی.
دیدیم که دگر باید از روی ویومدل نمونه بسازه، برای این کار باید نیازمندی های کلاس ویومدل رو تامین کنه، پس باید از روی UserManager
هم نمونه بسازه و بهمین ترتیب ...اگر در طی این فرآیند تو در تو، دگر به جایی برسه که ندونه چطور از روی یک کلاس نمونه بسازه و وابستگی رو تامین کنه خطا میده و میگه نمیتونم.
اپ را بیلد کنید. این کار باعث میشه که پردازنده انوتیشن دگر فعال بشه و کدهای مدیریت وابستگی هارو تولید کنه. با بیلد کردن شما خطای زیر رو در پنجره بیلد خواهید دید:
dagger/app/build/tmp/kapt3/stubs/debug/com/example/android/dagger/di/AppComponent.java:7: error: [Dagger/MissingBinding] com.example.android.dagger.storage.Storage cannot be provided without an @Provides-annotated method
اولین چیزی که می فهمیم اینه که در AppComponent
به خطا خورده است. دومین چیز اینه که خطا از نوع [Dagger/MissingBinding]
است و معنیش اینه که دگر نمیدونه چطور یک تایپ خاص رو پرواید کنه؟
اگر به خواندن ادامه بدیم می فهمیم که گفته Storage
نمیتونه بدون انوتیشن@Provides
تامین وابستگی بشه.
دلیلش مشخصه! ما هنوز به دگر نگفتیم چطور یک آبجکت از تایپ Storage
بسازه.
باید از راه متفاوتی ساخت آبجکت از Storage
رو به دگر بفهمونیم چون اینترفیسه و مستقیما نمیشه از روش نمونه ساخت. باید به دگر بگیم چه پیادهسازی از این اینترفیس رو لازم داریم که در اینجا SharedPreferencesStorage
است.
برای اینکار باید ماژول بسازیم. چطوری؟ یک کلاس مثلا به نام StorageModule
می سازیم و با انوتیشین @Module
مشخص اش میکنیم.مانند کامپوننتها، ماژولها هم مشخص میکنند چطور Dagger نیازمندی یک تایپ خاص رو تامین کنه؟ نیازمندی ها توسط @Provides
و @Binds
مشخص میشوند.
پس یک کلاس به نام StorageModule
میسازیم و با انوتیشن @Module
حاشیه نویسی اش میکنیم.
انوتیشن @Binds
برای تامین وابستگیهای اینترفیسی هست و پیاده سازی اینترفیسمون رو مشخص میکنه و همراه یک متد abstract استفاده میشه.
بنابراین درون ماژول یک فانکشن ابسترکت با نام دلخواه میسازیم مثلا provideStorage
و با @Binds
نشانهگذاریش می کنیم. و تایپ فانکشن هم میشه همون اینترفیسی که میخواهیم پیاده سازی شو فراهم کنیم که در این جا Storage
هست.
پیاده سازی اینترفیس چطور تعیین میشه؟
یک پارامتر از جنس پیادهسازی اینترفیس به اون متد abstarct می فرستیم که در اینجا SharedPreferencesStorage
هست.
با کد بالا به دَگِر میگیم هر موقع به آبجکت Storage
نیاز داشتی از SharedPreferencesStorage
استفاده کن.
چون متد ابسترکت هست، مجبوریم کلاس رو هم ابسترکت تعریف کنیم.
ماژولها روشی برای Encapsulate کردن تامین وابستگی آبجکتها به یک روش معنایی هستند.
تا اینجا به دگر گفتیم هروقت یک آبجکت Storage
درخواست شد، یک نمونه از SharedPreferencesStorage
بساز اما نگفتیم چطوری؟! برای اینکار از همان روش constructor Inject که یاد گرفتیم استفاده کنید. کلاس SharedPreferencesStorage
را باز کنید و تغییرش دهید.
گراف برنامه باید از این ماژول خبردار بشه. پس AppComponent رو باز کنید و داخل انوتیشین @Component
اسم این ماژول جدید را توی پارامتر modules اضافه کنید.
به این ترتیب AppComponent می تونه به اطلاعات درون ماژول دسترسی داشته باشه. در برنامههای بزرگتر باید یک NetworkModule برای تامین نیازمندی های OkHttpClient هم داشته باشیم یا برای کانفیگ Gson و Moshi.
پروژه رو بیلد کنید. دوباره یک ارور مشابه ارور قبلی خواهید دید که این دفعه می گه Context رو نمیتونم پیدا کنم. Context نیازمندی کدوم کلاس بود؟؟؟
میدونیم Context توسط سیستم اندروید provide میشه و خارج از گراف برنامه است. چون موقع ساخت گراف، Context از قبل وجود داره باید اونو به گراف پاس بدیم.
برای AppComponent یک فکتوری مینویسیم و از انوتیشن BindsInstance استفاده میکنیم.
1- یک اینترفیس به نام Factory می سازیم و براش انوتیشن Component.Factory میذاریم.
2- درون اینترفیس یک متد به نام create تعریف میکنیم که تایپ خروجی آن AppComponent است و یک پارامتر از جنس Context میگیرد که با انوتیشن BindsInstance انوتیت شده است.
انوتیشن BindsInstance برای آبجکتهایی هست که خارج گراف ساخته میشوند مانند Context .
دوباره پروژه رو بیلد کنید. با موفقیت بیلد میشه و دَگر گراف برنامه رو خواهد ساخت. ساخت گراف اپلیکیشن به طور اتوماتیک توسط پراسسور انوتیشن انجام میگیرد. نام کلاس تولید شده Dagger{ComponentName}
است و شامل پیاده سازی گراف است.
از کلاس DaggerAppComponent
تولید شده در قسمت بعدی استفاده خواهیم کرد. توجه کنید تا وقتی که کد با موفقیت بیلد نشود این کلاس را نخواهید داشت.
تا ایجا گراف برنامه این شکلیه:
همانگونه که میبینید AppComponent شامل StorageModule است، همراه با اطلاعاتی که میداند چطور از روی Storage آبجکت بسازد. Storage به Context وابسته است اما چون Context موقع ایجاد گراف ارسال میشود، همه وابستگیهای Storage تامین شده است.
یک instance از Context به متد create فکتوری AppComponent ارسال شده است، بنابراین هرآبجکتی در گراف که به Context نیاز داشته باشد، نمونه مشترکی در اختیارش قرار میگیرد. این موضوع با یک نقطه سفید در کنار مستطیل Context در تصویر مشخص شده است.
کُدلَب استفاده از دَگر در برنامه اندرویدی (4-تزریق گراف به اکتیوتی)
کُدلَب استفاده از دَگِر در برنامه اندرویدی (2-شروع)
کُدلَب استفاده از دَگِر در برنامه اندرویدی(1-معرفی)