کُدلَب استفاده از دَگِر در برنامه اندرویدی (3-انوتیشن‌ها)

در دو بخش قبل با DI و فریمورک Dagger آشنا شدید. هم چنین پروژه‌ای که قراره روش کار کنیم رو از گیتهاب کلون کردید و با پکیج ها و فیچرهاش آشنا شدید. در این بخش قراره Dagger رو روی فیچر یا جریان Registration پیاده‌سازی کنیم.آشنایی با انوتیشن هادگر همه‌ی کار رو با استفاده از انوتیشن‌ها پیش می بره، به همین دلیل باید باهاشون آشنا بشیم. اولین انوتیشنی که باهاش آشنا می‌شیم اسمش Inject !انوتیشن اینجکت - @Injectاز ریفکتور کردن جریان رجیستر کار رو آغاز می‌کنیم. بارها گفتیم که دگر قراره برامون گراف برنامه رو بسازه که توش مشخصه کی به چی ربط داره. برای این که بتونه اینکارو بکنه باید بدونه از کلاس‌هایی که تو گراف هستند چطور نمونه بسازه؟پس میاییم با یک انوتیشن @Injectسازنده اون کلاس‌ها رو علامت گذاری می‌کنیم. وقتی اینکارو می‌کنیم:1- دگر می فهمه چطور از آن کلاس نمونه بسازه2- اگر کلاس پارامتر ورودی داره، دگر می‌فهمه این کلاس مثلا آرگومان ایکس رو به عنوان وابستگی دریافت کرده.کلاس RegistrationViewModel.kt رو باز کنید. یادتونه که RegistrationActivityاین ویومدل رو می ساخت؟ واسه همین میخوایم طرز ساخت این ویومدل رو به دگر معرفی کنیم. RegistrationViewModel.ktهمون طور که می بینید در کاتلین حتما باید کی ورد constructorرو قبل از انوتیشن @Injectبیاریم. ویومدل کلاسUserManagerرو بعنوان وابستگی می‌گیره اما هنوز دگر خبر نداره چطور باید از روش نمونه بسازه و تو گراف بذاره، بهمین دلیل فایل UserManager رو هم باز کنید و انوتیشن @Injectرو برای سازنده کلاس بذارید.UserManager حالا دیگه دگر می‌فهمه که چطور از روی این دو تا کلاس نمونه بسازه. همون طور که می بینیدUserManager هم یک وابستگی داره که کلاس Storageهست اما Storageیک اینترفیسه و باید یه طور متفاوت به دگر یاد بدیم که چطور از روی اون نمونه بسازه. بعدا بهش می رسیم.ویوها و جایگاه شون در گراف! (اکتیویتی‌ها و فرگمنت‌ها)می دونید که اکتیوتی‌ها و فرگمنت‌ها توسط سیستم ساخته می‌شن و دگر نمی تونه از روشون نمونه بسازه. در اکتیویتی ها هرکدی که مربوط به مقدار دهی اولیه هست در متد on create قرار داره و ما نمی تونیم مثل قبل از از انوتیشین @Injectدر سازنده استفاده کنیم.پس چه کنیم؟فیلد اینجکشندر فیلد اینجکشن که عموما در اکتیویتی و فرگمنت استفاده می‌شه، فیلدهایی که وابستگی هستن و میخواهیم از دگر درخواست کنیم اونارو برامون بسازه رو با انوتیشین @Injectمشخص میکنیم. یادتونه که RegistrationActivityقرار بود ویومدل رو بسازه. پس بیایید با فیلد اینجکشن به دگر بگیم اونو برامون بسازه و این وظیفه رو از روی دوش اکتیویتی برداریم.برای رسیدن به این هدف دو تا کار می کنیم.1- در اکتیویتی جایی که یک فیلد از جنس ویومدل ایجاد کرده رو با انوتیشن @Injectمشخص میکنیم.2- خطی که مربوط به ساخت ویومدل هست رو از متد on create حذف میکنیم.یادآوریوقتی از @Injectدر سازنده یک کلاس استفاده می‌کنیم به دگر اطلاع می‌دهیم چطور از روی کلاس نمونه بسازه و وقتی از @Injectدر یک فیلد استفاده می‌کنیم به دگر اطلاع می دهیم که چطور فیلد رو با ساخت یک نمونه از آن تایپ پر کنه.بریم سراغ شناخت یک انوتیشن دیگه.انوتیشن کامپوننت - Componentدگر قراره گراف نیازمندی‌های پروژه رو بسازه و طوری اونو مدیریت کنه که ما بتونیم نیازمندی‌ها رو از این گراف بگیریم.برای رسیدن به این هدف یک اینترفیس می سازیم و با انوتیشن @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 بسازه.انوتیشن‌های Binds، Module و BindsInstanceباید از راه متفاوتی ساخت آبجکت از Storageرو به دگر بفهمونیم چون اینترفیسه و مستقیما نمیشه از روش نمونه ساخت. باید به دگر بگیم چه پیاده‌سازی از این اینترفیس رو لازم داریم که در اینجا SharedPreferencesStorageاست.ساخت دگر ماژولبرای اینکار باید ماژول بسازیم. چطوری؟ یک کلاس مثلا به نام StorageModule می سازیم و با انوتیشین @Moduleمشخص اش می‌کنیم.مانند کامپوننت‌ها، ماژول‌ها هم مشخص می‌کنند چطور Dagger نیازمندی یک تایپ خاص رو تامین کنه؟ نیازمندی ها توسط @Provides و @Binds مشخص می‌شوند.پس یک کلاس به نام StorageModuleمی‌سازیم و با انوتیشن @Moduleحاشیه نویسی اش می‌کنیم.ساخت ماژول برای معرفی اینترفیس به دَگِرانوتیشن Bindsانوتیشن @Binds برای تامین وابستگی‌های اینترفیسی هست و پیاده سازی اینترفیسمون رو مشخص می‌کنه و همراه یک متد abstract استفاده می‌شه.بنابراین درون ماژول یک فانکشن ابسترکت با نام دلخواه می‌سازیم مثلا provideStorageو با @Binds نشانه‌گذاریش می کنیم. و تایپ فانکشن هم میشه همون اینترفیسی که میخواهیم پیاده سازی شو فراهم کنیم که در این جا Storageهست.پیاده سازی اینترفیس چطور تعیین میشه؟یک پارامتر از جنس پیاده‌سازی اینترفیس به اون متد abstarct می فرستیم که در این‌جا SharedPreferencesStorageهست.با کد بالا به دَگِر می‌گیم هر موقع به آبجکت Storageنیاز داشتی از SharedPreferencesStorage استفاده کن.چون متد ابسترکت هست، مجبوریم کلاس رو هم ابسترکت تعریف کنیم.یادآوریماژول‌ها روشی برای Encapsulate کردن تامین وابستگی‌ آبجکت‌ها به یک روش معنایی هستند.تا این‌جا به دگر گفتیم هروقت یک آبجکت Storageدرخواست شد، یک نمونه از SharedPreferencesStorage بساز اما نگفتیم چطوری؟! برای اینکار از همان روش constructor Inject که یاد گرفتیم استفاده کنید. کلاس SharedPreferencesStorage را باز کنید و تغییرش دهید.تغییر AppComponentگراف برنامه باید از این ماژول خبردار بشه. پس AppComponent رو باز کنید و داخل انوتیشین @Componentاسم این ماژول جدید را توی پارامتر modules اضافه کنید.به این ترتیب AppComponent می تونه به اطلاعات درون ماژول دسترسی داشته باشه. در برنامه‌های بزرگتر باید یک NetworkModule برای تامین نیازمندی های OkHttpClient هم داشته باشیم یا برای کانفیگ Gson و Moshi.پروژه رو بیلد کنید. دوباره یک ارور مشابه ارور قبلی خواهید دید که این دفعه می گه Context رو نمیتونم پیدا کنم. Context نیازمندی کدوم کلاس بود؟؟؟انوتیشن BindsInstanceمیدونیم 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-معرفی)