Google
خانه / آموزش / کار با Dagger2 به زبان ساده – بخش دوم (پیشرفته)

کار با Dagger2 به زبان ساده – بخش دوم (پیشرفته)

سلام دوستان عزیز، متاسفانه کمی طول کشید تا مطلب آموزشی بگذارم، کامنت ها و استقبال شما انرژی خاصی به من داد تا هرچه زود تر نسبت به کامل کردن این بحث اقدام کنم. خیلی خوب، در مقاله قبلی با نحوه استفاده از Dagger2 آشنا شدیم. در این بخش می خواهیم چند روش دیگر استفاده را به همراه امکانات بیشتر این کتابخانه مورد برسی قرار دهیم. همانطور که در آموزش قبلی به روز کردم به علت استفاده زیاد از این کتابخانه، گوگل امکان اضافه کردن آن را به صورت مستقیم داده و شما می توانید ازین پس بصورت زیر در gradle در سطح خود برنامه از آن استفاده کنید:

می خواهیم کمی بیشتر با DI آشنا شویم و ابزار های آن را در Dagger2 ببینیم. ما چند نوع تزریق داریم.

Constructor injection

برروی کانستراکتور Inject@ گذاشته و در این صورت دیگر نیازی به نوشتن Module نداریم.

Fields injection

همینطور که در آموزش قبل ملاحظه کردید تزریق یک فیلد را گویند. ( در تمامی آموزش قبل از fields injection استفاده کردیم)

Methods injection

بروری یک متدی از یک کلاس Inject@ بگذاریم.

 

چند انوتیشن که باید در این جلسه با آنها آشنا شویم:
@Scope annotation

اصولا هدف استفاده از این انوتیشن معرفی Scope آبجک تولید شده است. یعنی Dagger2 به شما این امکان را می دهد تا Object های شما چرخه عمر متفاوت داشته باشند و شما فقط تعریف می کنید، مدیریتش با خود Dagger است. پیشنهاد من این است که برای این مقاله تا همین حد کافی است و بعد از اتمام این اموزش به مقاله کامل مراجعه کنید.

@Qualifier

وقتی از این انوتیشن استفاده می کنیم که متد ها همنام و و ورودی خروجی یکسان داشته باشند و به Dagger می فهمانیم کِی، چی را تزریق کند.

بسیار عالی. در آموزش قبل دیدیم که هرجا بخواهیم از DI توسط Dagger2 استفاده کنیم باید سه مرحله زیر را انجام دهیم:

۱- ساخت Provider ها در کلاس Module که مشخص می کنیم کلاس ها چگونه تولید شوند.

۲- ساخت اینترفیسی به نام Component که می گویم از کدام Module ها استفاده کند و متدی در آن قرار می دادیم که کلاسی که در آن Inject انجام شده بود وجود داشته باشد تا Dagger2 بفهمد کدام کلاس ها را جهت فیلد انجکشن اسکن و وایراپ کند و متوجه شدیم که Dagger متدها رو بصورت خودکار تولید می کند.

۳- در کلاس استفاده کننده ( قرار است Inject اتفاق بیافتد) متد تعریف شده در Component را از کلاس AutoGenerated توسط Dagger صدا بزنیم و  تمام!

اگر هنوز بخش مبهمی مانده با در نظر گرفتن سه مرحله بالا آموزش قبل را دوباره مطالعه کنید.

حال یک سوال:

با توجه به مطالب یاد شده می توانیم هر بخش از برنامه را بصورت DI توسط Dagger2 بنویسیم. مثلا یکسری کلاس هایی را بصورت Inject در کلاس های دیگر استفاده کنید. اما سوال من این است که اگر بخواهیم در کل پروژه از DI استفاده کنیم چه باید کرد؟ یعنی کلاس هایی که به همدیگر ربط پیدا می کنند. (مثلا Context که در خیلی جاها لازم داریم و یا SharedPrefrence و یا خیلی کلاس های دیگر) این سوالی بود که برای خودم هم پیش آمد و بهترین جواب برای آن طراحی از بالا به پایین کل پروژه است! یعنی به جای اینکه بیاییم  از پایین به بالا برای تک تک کلاس های استفاده شده Module بنویسیم و سپس برسیم به سطح کامپوننت های اندروید مثل fragment و Activity که واقعاً نشدنی است و ارتباطات خیلی پیچیده می شود و از طرفی Inject های تکراری ایجاد می شد که Dagger2 آن را قبول نمی کند، بهتر است از سطح Application به پایین طراحی کنیم. این نکته را هم در نظر داشته باشید که Dagger2 در یک DI مجزا، امکان ساخت یک کامپوننت اصلی را می دهد ( به شکل بالا توجه کنید) و اگر کامپوننت های دیگری داشتیم باید در ارتباط با کامپوننت اصلی باشند.  ( این ارتباط یا از نوع Dependency است یا SubComponent که در انتهای این بحث به تفاوت آنها اشاره خواهم کرد.) البته این مساله در صورتی است که Dependency Injection های مرتبط به هم داشته باشید. اما اگر فقط می خواهید در بخش های خاصی از برنامه از DI استفاده کنید که به هم مرتبط نیستند هر کدام Component و Module مستقل از هم را خواهند داشت که مشکلی هم پیش نمیاید. مثلا در مساله آموزش قبل که DI را برای تولید خودرو استفاده کردیم، فرض کنید یک DI هم برای قایق یا کشتی بنویسیم که بخواهیم تولید قایق را برایمان مدیریت کند. برای این کار دقیقاً مانند DI خودرو Module و Component مربوطه آن را تشکیل می دهیم و ربطی هم به DI خودرو ندارد و از هم مستقل اند. تنها نقطه مشترک کلاسی هست که استفاده کننده است. ( مانند Activity)

 

می خواهیم مثالی ساده نوشته و  آن را باهم برسی کنیم. لطفاً به این بخش خیلی توجه کنید. به علت ارتباط بخش های مختلف ممکن است پیچیدگی ظاهری داشته باشد، اما واقعاً پیچیده نیست و تمامی قوانینی که در جلسه قبل گفتیم صادق است. تلاش من این است که تمامی حالت های ممکن برای ساخت DI را برسی کنیم تا تمامی سوالات برطرف شود.

شرح برنامه:

برنامه ای داریم که با استفاده از اطلاعات کاربری، درخواستی به وب زده و نتیجه را در پایگاه داده ذخیره می کند. توجه کنید که اتفاقات بصورت شماتیک است و هدف برسی روابط آنها در Dagger2 می باشد و صرفاً به ازای اکثر عملیات ها در کد، فقط لاگ کرده ایم. به شکل زیر دقت کنید: کلاس اپلیکیشن دارای کلاس های Context و User و Database (که وابسته به Context  و User است) و Activity است و خود کلاس Activity هم شامل NetworkCalss است که  وابسته به User است.

نکات برنامه:

کلاس Application به Database نیاز دارد تا آن را در اول برنامه Initilize کند از طرفی Database به User و Context نیاز دارد. سپس Activity یک Network Request می زند و نتیجه را در Database ذخیره می کند.اول از همه Module های مربوط به User و Database را ایجاد می کنیم تا Dagger بفهمد آنها را چگونه تولید کند.

سوال: چه چیزی را می خواهیم تزریق کنیم؟ جواب: DataBase و User

سوال: چطور ساخته می شود؟ جواب: در UserModule  و DatabaseModule مشخص شده است و آنها را به عنوان Module به Component داده ایم!

سوال: استفاده کننده نهایی کیست؟ جواب: DaggerTestApplication  است بنابر این یک متد با ورودی خودش نوشتیم تا Inject@ های روی فیلدهایش اسکن شود.

سوال: دوسه تا متد بعدی چیه؟ جواب: باید برای هر کلاسی که child ها به آن نیاز دارند یک متد Exporter  بنویسیم.

از آنجایی که می خواهم کلاس NetworkClass را فقط در Activity استفاده کنم و از طرفی NetworkClass به User نیاز دارد بنابراین یک کامپوننت جدا با Scope جدید می نویسیم. چون می خواهم مادامی که Activity وجود دارد NetworkClass نیز وجود داشته باشد. اما لازم است تا Component اصلی متدهایی داشته باشند تا کلاس User را برایم Export کنند. بنابر این من متدی اضافه کردم به نام provideUser که خروجی آن User است. خود Dagger پیداده سازی آن را مدیریت می کند. همچنین چون ممکن است Context برنامه را کلاس های دیگر بخواهند یک متد نیز برای آن نوشتیم و همچنین برای Database هم یک متد Export کننده نوشتم.( خواهید دید که DataBase در Activity جهت ذخیره مورد نیاز قرار خواهد گرفت. پس یک متد Export کننده لازم داشت.) این طراحی بالا به پایینی بود که اول مقاله گفتم. یعنی در Component اصلی که نحوه ارتباط ها را مشخص می کنیم برای فرزند ها نیز وابستگی های مشترک را در نظر بگیریم.

یک نکته دیگر در مورد context است. از آنجایی که کلاس های دیگری ممکن است به آن وابسته شوند یک متد برای آن نوشتیم، اما چون Context را ما نمی سازیم و خود سیستم تولید می کند لازم نیست برایش Module بنویسیم و ساخت و Handle آن توسط خود Dagger با Android system انجام می شود. البته در این پروژه استفاده ای از آن نکردیم، اما مطمئناً به آن نیاز پیدا خواهید کرد.

حال می رویم سراغ وابستگی های Activity و این بخش است که کمی برایمان تازگی دارد.

سوال: چه چیزی را می خواهیم تزریق کنیم؟ جواب: NetworkClass

سوال: چطور ساخته می شود؟ جواب: در ServiceModule مشخص شده است و آن را به عنوان Module به Component داده ایم!

سوال: اما نحوه ساخت یوزر را در ServiceModule  نداریم؟! جواب: در ActivityComponent  مشخص کردیم که

dependencies = ApplicationComponent.class ( یعنی وابستگی به ApplicationComponent داریم و هر کلاسی که در آن Export شده است را می توانیم استفاده کنیم. پس ServiceModule در ساخت NetworkClass یک User به عنوان ورودی می گیرد، و نحوه ساخت آن یوزر را ApplicationComponent  می داند.

سوال: چرا Scope جدید دادیم؟ جواب: برای اینکه می خواهیم مادامی که استفاده کننده آن (Activity ) وجود دارد، NetworkClass ما وجود داشته باشد. نحوه دقیق کار Scope در این آموزش موجود است.

سوال: این NetworkClass کجا استفاده می شود و Dagger از کجا می فهمد؟ جواب: این جواب رو دیگه شما هم می دانید و همانطور که در کلاس Activity ملاحظه می کنید چون درون دوتا فیلد اینجکشن داریم ( یکی NetworkClass و دیگری DataBase)  بنابر این یک متدی در Component قرار دادم تا ورودی آن از جنس MainActivity باشد تا تمامی Field injection های آن اسکن شود و ارتباط ها برقرار شود.

ملاحظه خواهید کرد که هم NetworkClass و هم DataBase به درستی در Activity تزریق شده اند و قابل استفاده هستند. حالا که نحوه کار با Dagger2 را متوجه شدیم می توانیم برای متوجه شدن بیشتر این کد را به چند صورت متفاوت نوشت و تغییر داد. که همه اینها در Github در شاخه های با نام های:lesson2, lesson2-type2, lesson2-type3, lesson2-subcomponent قرار گرفته اند.

 

روش های دیگر برای درک کامل

برای اینکه دقیقاً بفهمید Dagger2 چطور کار می کند حتماً این مثال ها را ببینید و درک کنید. در این چند مثال انواع روش های پیاده سازی را انجام داده ام و فهمیدن انها باعث از بین رفتن گنگی نسبی قضیه و تسلط کامل خواهد شد و جواب بسیاری از سوال هایی که در ذهن دارید را خواهید یافت. تلاشم تغییر روی یک کلاس واحد Database بود تا با ثابت بودن آن تغییر روش ها را ملاحظه نمایید.

شاخه lesson2 : همین کدی است که در این آموزش داشتیم.

شاخه lesson2-type2 :در کلاس Database خود User تزریق شده است. بنابراین ناچاریم تا متدی بنویسیم که ورودی آن Database باشد تا فیلد های تزریق شده اسکن شود.

شاخه lesson2-type3: در کلاس Database از Constructor Injection استفاده کردم. همانطور که در تعریف اول بحث مطرح کردم در این روش Inject@ در روی Constructor  کلاس می گذاریم و دیگر نیازی به نوشتن Module برای نحوه پیاده سازی آن نداریم و کلا DatabaseModule حذف می شود. البته ActivityComponent تغییری نمی کند.

شاخه lesson2-subcomponent : مثالی است در مورد مطلب مهمی که در اواسط آموزش به آن اشاره کردم:

Subcomponent vs Dependency

همانطور که در مثال ملاحظه کردید برای ارتباط ActivityComponent با ApplicationComponent از Dependencies استفاده کردیم

و سپس هر Object که لازم بود را در ApplicationComponent برایش یک متد exporter نوشتیم. اما راه دیگر استفاده به عنوان Subcomponent است. در این روش انترفیس subcomponent به همه Object های Component دسترسی دارد و نیازی به نوشتن متد های exporter ندارد و کارایی آن هنگامی است که واقعاً به تمامی object ها نیاز داریم. برای شناساندن Subcomponent به Component اصلی فقط کافیست که یک متد Factory برایش در Component بنویسیم.

و برای استفاده wire-up کردن هم متد ماژول ان را از کامپوننت اصلی بگیریم. ( خط ۲۷)

امیدوارم تونسته باشم مطلب رو خوب بیان کنم. در صورت وجود هر گونه مشکل یا سوال یا بحث، که واقعاً این مبحث نیاز به بحث داره، راحت باشید و کامنت بگذارید.

github_download

بعد از گرفتن پروژه یکبار اون رو import کنید تا قابل اجرا بشه.

۱ دیدگاه

  1. متشکرم از آموزش جالبتون. سخته

دیدگاهتان را ثبت کنید

آدرس ایمیل شما منتشر نخواهد شدعلامتدارها لازمند *

*

bigtheme