صفحه اصول اولیه Dagger توضیح داد که چگونه Dagger می تواند به شما در خودکارسازی تزریق وابستگی در برنامه خود کمک کند. با Dagger، نیازی به نوشتن کدهای خسته کننده و مستعد خطای دیگ بخار ندارید.
خلاصه بهترین شیوه ها
- از تزریق سازنده با
@Inject
برای اضافه کردن انواع به نمودار Dagger هر زمان که ممکن است استفاده کنید. وقتی اینطور نیست:- از
@Binds
استفاده کنید تا به Dagger بگویید که یک رابط باید کدام پیاده سازی را داشته باشد. - از
@Provides
استفاده کنید تا به Dagger بگویید چگونه کلاس هایی را ارائه کند که پروژه شما متعلق به آنها نیست.
- از
- ماژول ها را فقط یک بار در یک جزء باید اعلام کنید.
- بسته به طول عمری که حاشیه نویسی در آن استفاده می شود، حاشیه نویسی دامنه را نام ببرید. به عنوان مثال میتوان به
@ApplicationScope
،@LoggedUserScope
و@ActivityScope
اشاره کرد.
افزودن وابستگی ها
برای استفاده از Dagger در پروژه خود، این وابستگی ها را به برنامه خود در فایل build.gradle
خود اضافه کنید. می توانید آخرین نسخه Dagger را در این پروژه GitHub پیدا کنید.
کاتلین
plugins { id 'kotlin-kapt' } dependencies { implementation 'com.google.dagger:dagger:2.x' kapt 'com.google.dagger:dagger-compiler:2.x' }
جاوا
dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }
خنجر در اندروید
یک نمونه برنامه اندروید را با نمودار وابستگی از شکل 1 در نظر بگیرید.
در اندروید، معمولاً یک نمودار Dagger ایجاد میکنید که در کلاس برنامه شما زندگی میکند، زیرا میخواهید یک نمونه از نمودار تا زمانی که برنامه در حال اجرا است در حافظه باشد. به این ترتیب نمودار به چرخه عمر اپلیکیشن متصل می شود. در برخی موارد، ممکن است بخواهید زمینه برنامه را در نمودار نیز در دسترس داشته باشید. برای آن، شما همچنین باید گراف را در کلاس Application
قرار دهید. یکی از مزایای این روش این است که نمودار برای سایر کلاس های فریم ورک اندروید در دسترس است. علاوه بر این، با استفاده از یک کلاس Application
سفارشی در تست ها، آزمایش را ساده می کند.
از آنجا که رابطی که نمودار را ایجاد می کند با @Component
حاشیه نویسی شده است، می توانید آن را ApplicationComponent
یا ApplicationGraph
بنامید. همانطور که در قطعه کد زیر نشان داده شده است، معمولاً نمونه ای از آن مؤلفه را در کلاس Application
سفارشی خود نگه می دارید و هر بار که به نمودار برنامه نیاز داشتید، آن را فراخوانی می کنید:
کاتلین
// Definition of the Application graph @Component interface ApplicationComponent { ... } // appComponent lives in the Application class to share its lifecycle class MyApplication: Application() { // Reference to the application graph that is used across the whole app val appComponent = DaggerApplicationComponent.create() }
جاوا
// Definition of the Application graph @Component public interface ApplicationComponent { } // appComponent lives in the Application class to share its lifecycle public class MyApplication extends Application { // Reference to the application graph that is used across the whole app ApplicationComponent appComponent = DaggerApplicationComponent.create(); }
از آنجایی که برخی کلاسهای فریمورک اندروید مانند فعالیتها و قطعات توسط سیستم نمونهسازی میشوند، Dagger نمیتواند آنها را برای شما ایجاد کند. برای فعالیت ها به طور خاص، هر کد اولیه باید وارد متد onCreate()
شود. این بدان معناست که شما نمی توانید مانند نمونه های قبلی از حاشیه نویسی @Inject
در سازنده کلاس (تزریق سازنده) استفاده کنید. در عوض، باید از تزریق میدانی استفاده کنید.
به جای ایجاد وابستگی های مورد نیاز یک فعالیت در متد onCreate()
، می خواهید Dagger آن وابستگی ها را برای شما پر کند. برای تزریق فیلد، به جای آن، حاشیه نویسی @Inject
را در فیلدهایی که می خواهید از نمودار Dagger دریافت کنید، اعمال کنید.
کاتلین
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject lateinit var loginViewModel: LoginViewModel }
جاوا
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject LoginViewModel loginViewModel; }
برای سادگی، LoginViewModel
یک ViewModel اجزای معماری اندروید نیست. این فقط یک کلاس معمولی است که به عنوان ViewModel عمل می کند. برای اطلاعات بیشتر درباره نحوه تزریق این کلاسها، کد موجود در اجرای رسمی Android Blueprints Dagger را در شاخه dev-dagger بررسی کنید.
یکی از ملاحظات Dagger این است که فیلدهای تزریق شده نمی توانند خصوصی باشند. آنها باید حداقل قابلیت مشاهده بسته خصوصی مانند کد قبلی را داشته باشند.
فعالیت های تزریقی
Dagger باید بداند که LoginActivity
باید به نمودار دسترسی داشته باشد تا ViewModel
مورد نیاز خود را ارائه دهد. در صفحه مبانی Dagger ، از واسط @Component
برای دریافت اشیاء از نمودار با نمایش توابعی با نوع بازگشتی آنچه میخواهید از نمودار دریافت کنید، استفاده کردید. در این مورد، شما باید به Dagger در مورد یک شی ( LoginActivity
در این مورد) که نیاز به یک وابستگی برای تزریق دارد، بگویید. برای آن، تابعی را در معرض دید قرار می دهید که شی درخواست تزریق را به عنوان پارامتر می گیرد.
کاتلین
@Component interface ApplicationComponent { // This tells Dagger that LoginActivity requests injection so the graph needs to // satisfy all the dependencies of the fields that LoginActivity is requesting. fun inject(activity: LoginActivity) }
جاوا
@Component public interface ApplicationComponent { // This tells Dagger that LoginActivity requests injection so the graph needs to // satisfy all the dependencies of the fields that LoginActivity is injecting. void inject(LoginActivity loginActivity); }
این تابع به Dagger می گوید که LoginActivity
می خواهد به نمودار دسترسی داشته باشد و درخواست تزریق می کند. Dagger باید تمام وابستگیهایی را که LoginActivity
به آن نیاز دارد برآورده کند ( LoginViewModel
با وابستگیهای خودش). اگر چندین کلاس دارید که درخواست تزریق دارند، باید به طور خاص همه آنها را با نوع دقیق آنها در کامپوننت اعلام کنید. برای مثال، اگر LoginActivity
و RegistrationActivity
درخواست تزریق داشتند، به جای یک روش عمومی که هر دو مورد را پوشش میدهد، دو متد inject()
خواهید داشت. یک متد inject()
عمومی به Dagger نمی گوید که چه چیزی باید ارائه شود. توابع موجود در اینترفیس می توانند هر نامی داشته باشند، اما فراخوانی آنها inject()
هنگامی که آنها شیء را برای تزریق به عنوان پارامتر دریافت می کنند، یک قرارداد در Dagger است.
برای تزریق یک شی در اکتیویتی، میتوانید از appComponent
تعریفشده در کلاس Application
خود استفاده کنید و متد inject()
را فراخوانی کنید و در نمونهای از اکتیویتی که درخواست تزریق میکند ارسال کنید.
هنگام استفاده از اکتیویتی ها، قبل از فراخوانی () super.onCreate()
، Dagger را در متد onCreate()
) اکتیویتی تزریق کنید تا از مشکلات مربوط به بازیابی قطعه جلوگیری کنید. در طول مرحله بازیابی در super.onCreate()
یک اکتیویتی قطعاتی را که ممکن است بخواهند به اتصالات اکتیویتی دسترسی داشته باشند متصل می کند.
هنگام استفاده از قطعات، Dagger را در متد onAttach()
قطعه تزریق کنید. در این مورد، این کار را می توان قبل یا بعد از فراخوانی super.onAttach()
انجام داد.
کاتلین
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Make Dagger instantiate @Inject fields in LoginActivity (applicationContext as MyApplication).appComponent.inject(this) // Now loginViewModel is available super.onCreate(savedInstanceState) } } // @Inject tells Dagger how to create instances of LoginViewModel class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
جاوا
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { // Make Dagger instantiate @Inject fields in LoginActivity ((MyApplication) getApplicationContext()).appComponent.inject(this); // Now loginViewModel is available super.onCreate(savedInstanceState); } } public class LoginViewModel { private final UserRepository userRepository; // @Inject tells Dagger how to create instances of LoginViewModel @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
بیایید به Dagger بگوییم که چگونه بقیه وابستگیها را برای ساخت نمودار فراهم کند:
کاتلین
class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor( private val loginService: LoginRetrofitService ) { ... }
جاوا
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } } public class UserLocalDataSource { @Inject public UserLocalDataSource() {} } public class UserRemoteDataSource { private final LoginRetrofitService loginRetrofitService; @Inject public UserRemoteDataSource(LoginRetrofitService loginRetrofitService) { this.loginRetrofitService = loginRetrofitService; } }
ماژول های خنجر
برای این مثال، شما از کتابخانه شبکه Retrofit استفاده می کنید. UserRemoteDataSource
به LoginRetrofitService
وابستگی دارد. با این حال، روش ایجاد یک نمونه از LoginRetrofitService
با آنچه تاکنون انجام داده اید متفاوت است. این یک نمونه کلاسی نیست. این نتیجه فراخوانی Retrofit.Builder()
و ارسال پارامترهای مختلف برای پیکربندی سرویس ورود است.
به غیر از حاشیه نویسی @Inject
، راه دیگری برای گفتن به Dagger وجود دارد که چگونه یک نمونه از یک کلاس را ارائه دهد: اطلاعات داخل ماژول های Dagger. ماژول Dagger کلاسی است که با @Module
حاشیه نویسی شده است. در آنجا میتوانید وابستگیها را با حاشیهنویسی @Provides
تعریف کنید.
کاتلین
// @Module informs Dagger that this class is a Dagger Module @Module class NetworkModule { // @Provides tell Dagger how to create instances of the type that this function // returns (i.e. LoginRetrofitService). // Function parameters are the dependencies of this type. @Provides fun provideLoginRetrofitService(): LoginRetrofitService { // Whenever Dagger needs to provide an instance of type LoginRetrofitService, // this code (the one inside the @Provides method) is run. return Retrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService::class.java) } }
جاوا
// @Module informs Dagger that this class is a Dagger Module @Module public class NetworkModule { // @Provides tell Dagger how to create instances of the type that this function // returns (i.e. LoginRetrofitService). // Function parameters are the dependencies of this type. @Provides public LoginRetrofitService provideLoginRetrofitService() { // Whenever Dagger needs to provide an instance of type LoginRetrofitService, // this code (the one inside the @Provides method) is run. return new Retrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService.class); } }
ماژول ها راهی برای جمع بندی معنایی اطلاعات در مورد نحوه ارائه اشیا هستند. همانطور که می بینید، شما کلاس NetworkModule
را برای گروه بندی منطق ارائه اشیاء مرتبط با شبکه فراخوانی کردید. اگر برنامه گسترش یابد، میتوانید نحوه ارائه OkHttpClient
یا نحوه پیکربندی Gson یا Moshi را نیز در اینجا اضافه کنید.
وابستگی های یک متد @Provides
پارامترهای آن متد هستند. برای روش قبلی، LoginRetrofitService
را می توان بدون وابستگی ارائه کرد زیرا این روش هیچ پارامتری ندارد. اگر یک OkHttpClient
به عنوان پارامتر اعلام کرده بودید، Dagger باید یک نمونه OkHttpClient
را از نمودار ارائه دهد تا وابستگی های LoginRetrofitService
را برآورده کند. به عنوان مثال:
کاتلین
@Module class NetworkModule { // Hypothetical dependency on LoginRetrofitService @Provides fun provideLoginRetrofitService( okHttpClient: OkHttpClient ): LoginRetrofitService { ... } }
جاوا
@Module public class NetworkModule { @Provides public LoginRetrofitService provideLoginRetrofitService(OkHttpClient okHttpClient) { ... } }
برای اینکه نمودار Dagger از این ماژول مطلع شود، باید آن را به صورت زیر به واسط @Component
اضافه کنید:
کاتلین
// The "modules" attribute in the @Component annotation tells Dagger what Modules // to include when building the graph @Component(modules = [NetworkModule::class]) interface ApplicationComponent { ... }
جاوا
// The "modules" attribute in the @Component annotation tells Dagger what Modules // to include when building the graph @Component(modules = NetworkModule.class) public interface ApplicationComponent { ... }
روش پیشنهادی برای افزودن انواع به نمودار Dagger با استفاده از تزریق سازنده (یعنی با حاشیه نویسی @Inject
در سازنده کلاس) است. گاهی اوقات، این امکان پذیر نیست و باید از ماژول های Dagger استفاده کنید. یک مثال زمانی است که می خواهید Dagger از نتیجه یک محاسبات برای تعیین نحوه ایجاد یک نمونه از یک شی استفاده کند. هر زمان که باید نمونه ای از آن نوع ارائه کند، Dagger کد را در متد @Provides
اجرا می کند.
نمودار Dagger در مثال در حال حاضر به این صورت است:
نقطه ورود به نمودار LoginActivity
است. از آنجایی که LoginActivity
LoginViewModel
تزریق می کند، Dagger نموداری می سازد که می داند چگونه نمونه ای از LoginViewModel
و به صورت بازگشتی از وابستگی های آن ارائه کند. Dagger به دلیل حاشیه نویسی @Inject
در سازنده کلاس ها می داند چگونه این کار را انجام دهد.
در داخل ApplicationComponent
تولید شده توسط Dagger، یک روش کارخانه ای برای دریافت نمونه هایی از تمام کلاس هایی که می داند چگونه ارائه کند وجود دارد. در این مثال، Dagger به NetworkModule
موجود در ApplicationComponent
تفویض میکند تا نمونهای از LoginRetrofitService
را دریافت کند.
دامنه های خنجر
دامنه ها در صفحه مبانی Dagger به عنوان راهی برای داشتن یک نمونه منحصر به فرد از یک نوع در یک جزء ذکر شده است. این همان چیزی است که از محدوده بندی یک نوع در چرخه عمر قطعه استفاده می شود.
از آنجا که ممکن است بخواهید از UserRepository
در سایر ویژگیهای برنامه استفاده کنید و ممکن است نخواهید هر بار که به آن نیاز دارید یک شی جدید ایجاد کنید، میتوانید آن را به عنوان یک نمونه منحصر به فرد برای کل برنامه تعیین کنید. برای LoginRetrofitService
هم همینطور است: ایجاد آن میتواند گران باشد، و همچنین میخواهید یک نمونه منحصربهفرد از آن شی مورد استفاده مجدد قرار گیرد. ایجاد یک نمونه از UserRemoteDataSource
آنقدر گران نیست، بنابراین محدود کردن آن به چرخه عمر مؤلفه ضروری نیست.
@Singleton
تنها حاشیه نویسی دامنه است که با بسته javax.inject
ارائه می شود. می توانید از آن برای حاشیه نویسی ApplicationComponent
و اشیایی که می خواهید در کل برنامه استفاده مجدد کنید استفاده کنید.
کاتلین
@Singleton @Component(modules = [NetworkModule::class]) interface ApplicationComponent { fun inject(activity: LoginActivity) } @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } @Module class NetworkModule { // Way to scope types inside a Dagger Module @Singleton @Provides fun provideLoginRetrofitService(): LoginRetrofitService { ... } }
جاوا
@Singleton @Component(modules = NetworkModule.class) public interface ApplicationComponent { void inject(LoginActivity loginActivity); } @Singleton public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } } @Module public class NetworkModule { @Singleton @Provides public LoginRetrofitService provideLoginRetrofitService() { ... } }
مراقب باشید هنگام اعمال scope روی اشیا، نشت حافظه ایجاد نشود. تا زمانی که مولفه scoped در حافظه است، شی ایجاد شده نیز در حافظه است. از آنجایی که ApplicationComponent
هنگام راهاندازی برنامه (در کلاس Application
) ایجاد میشود، زمانی که برنامه از بین میرود، از بین میرود. بنابراین، نمونه منحصر به فرد UserRepository
همیشه در حافظه باقی می ماند تا زمانی که برنامه از بین برود.
اجزای فرعی خنجر
اگر جریان ورود به سیستم شما (که توسط یک LoginActivity
مدیریت می شود) از چند قطعه تشکیل شده است، باید از همان نمونه LoginViewModel
در همه قطعات استفاده مجدد کنید. @Singleton
نمی تواند LoginViewModel
برای استفاده مجدد از نمونه به دلایل زیر حاشیه نویسی کند:
نمونه
LoginViewModel
پس از پایان جریان در حافظه باقی می ماند.شما یک نمونه متفاوت از
LoginViewModel
برای هر جریان ورود می خواهید. به عنوان مثال، اگر کاربر از سیستم خارج شود، شما یک نمونه متفاوت ازLoginViewModel
را می خواهید، به جای نمونه مشابه زمانی که کاربر برای اولین بار وارد سیستم شده است.
برای گسترش LoginViewModel
به چرخه حیات LoginActivity
، باید یک جزء جدید (یک زیرگراف جدید) برای جریان ورود و یک محدوده جدید ایجاد کنید.
بیایید یک نمودار خاص برای جریان ورود ایجاد کنیم.
کاتلین
@Component interface LoginComponent {}
جاوا
@Component public interface LoginComponent { }
اکنون، LoginActivity
باید از LoginComponent
تزریق شود زیرا دارای یک پیکربندی خاص برای ورود است. این کار مسئولیت تزریق LoginActivity
از کلاس ApplicationComponent
حذف می کند.
کاتلین
@Component interface LoginComponent { fun inject(activity: LoginActivity) }
جاوا
@Component public interface LoginComponent { void inject(LoginActivity loginActivity); }
LoginComponent
باید بتواند به اشیاء از ApplicationComponent
دسترسی داشته باشد زیرا LoginViewModel
به UserRepository
بستگی دارد. روشی که میتوان به Dagger گفت که میخواهید یک مؤلفه جدید از بخشی از مؤلفه دیگر استفاده کند، با مؤلفههای فرعی Dagger است. مؤلفه جدید باید جزء فرعی مؤلفه حاوی منابع مشترک باشد.
مولفه های فرعی اجزایی هستند که گراف شی یک جزء اصلی را به ارث می برند و گسترش می دهند. بنابراین، تمام اشیاء ارائه شده در مؤلفه والد در زیر مؤلفه نیز ارائه می شوند. به این ترتیب، یک شی از یک جزء فرعی می تواند به یک شی ارائه شده توسط مولفه والد وابسته باشد.
برای ایجاد نمونه هایی از مولفه های فرعی، به یک نمونه از مولفه والد نیاز دارید. بنابراین، اشیاء ارائه شده توسط مؤلفه والد به مؤلفه فرعی همچنان در محدوده مؤلفه والد قرار دارند.
در مثال، باید LoginComponent
به عنوان زیرمجموعه ApplicationComponent
تعریف کنید. برای انجام این کار، LoginComponent
با @Subcomponent
حاشیه نویسی کنید:
کاتلین
// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent @Subcomponent interface LoginComponent { // This tells Dagger that LoginActivity requests injection from LoginComponent // so that this subcomponent graph needs to satisfy all the dependencies of the // fields that LoginActivity is injecting fun inject(loginActivity: LoginActivity) }
جاوا
// @Subcomponent annotation informs Dagger this interface is a Dagger Subcomponent @Subcomponent public interface LoginComponent { // This tells Dagger that LoginActivity requests injection from LoginComponent // so that this subcomponent graph needs to satisfy all the dependencies of the // fields that LoginActivity is injecting void inject(LoginActivity loginActivity); }
همچنین باید یک کارخانه فرعی در داخل LoginComponent
تعریف کنید تا ApplicationComponent
بداند که چگونه نمونه هایی از LoginComponent
ایجاد کند.
کاتلین
@Subcomponent interface LoginComponent { // Factory that is used to create instances of this subcomponent @Subcomponent.Factory interface Factory { fun create(): LoginComponent } fun inject(loginActivity: LoginActivity) }
جاوا
@Subcomponent public interface LoginComponent { // Factory that is used to create instances of this subcomponent @Subcomponent.Factory interface Factory { LoginComponent create(); } void inject(LoginActivity loginActivity); }
برای اینکه به Dagger بگویید که LoginComponent
یک جزء فرعی از ApplicationComponent
است، باید آن را با موارد زیر مشخص کنید:
ایجاد یک ماژول Dagger جدید (به عنوان مثال
SubcomponentsModule
) که کلاس زیرمجموعه را به ویژگیsubcomponents
حاشیه نویسی منتقل می کند.کاتلین
// The "subcomponents" attribute in the @Module annotation tells Dagger what // Subcomponents are children of the Component this module is included in. @Module(subcomponents = LoginComponent::class) class SubcomponentsModule {}
جاوا
// The "subcomponents" attribute in the @Module annotation tells Dagger what // Subcomponents are children of the Component this module is included in. @Module(subcomponents = LoginComponent.class) public class SubcomponentsModule { }
اضافه کردن ماژول جدید (یعنی
SubcomponentsModule
) بهApplicationComponent
:کاتلین
// Including SubcomponentsModule, tell ApplicationComponent that // LoginComponent is its subcomponent. @Singleton @Component(modules = [NetworkModule::class, SubcomponentsModule::class]) interface ApplicationComponent { }
جاوا
// Including SubcomponentsModule, tell ApplicationComponent that // LoginComponent is its subcomponent. @Singleton @Component(modules = {NetworkModule.class, SubcomponentsModule.class}) public interface ApplicationComponent { }
توجه داشته باشید که
ApplicationComponent
دیگر نیازی به تزریقLoginActivity
ندارد زیرا این مسئولیت اکنون بهLoginComponent
تعلق دارد، بنابراین می توانید متدinject()
ازApplicationComponent
حذف کنید.مصرف کنندگان
ApplicationComponent
باید بدانند که چگونه نمونه هایی ازLoginComponent
ایجاد کنند. مؤلفه والد باید روشی را در رابط خود اضافه کند تا به مصرف کنندگان اجازه دهد نمونه هایی از مولفه فرعی را از نمونه ای از مؤلفه والد ایجاد کنند:کارخانه ای که نمونه هایی از
LoginComponent
را در رابط ایجاد می کند را در معرض نمایش قرار دهید:کاتلین
@Singleton @Component(modules = [NetworkModule::class, SubcomponentsModule::class]) interface ApplicationComponent { // This function exposes the LoginComponent Factory out of the graph so consumers // can use it to obtain new instances of LoginComponent fun loginComponent(): LoginComponent.Factory }
جاوا
@Singleton @Component(modules = { NetworkModule.class, SubcomponentsModule.class} ) public interface ApplicationComponent { // This function exposes the LoginComponent Factory out of the graph so consumers // can use it to obtain new instances of LoginComponent LoginComponent.Factory loginComponent(); }
اختصاص دامنه به اجزای فرعی
اگر پروژه را می سازید، می توانید نمونه هایی از ApplicationComponent
و LoginComponent
ایجاد کنید. ApplicationComponent
به چرخه عمر برنامه متصل است زیرا می خواهید تا زمانی که برنامه در حافظه است از همان نمونه نمودار استفاده کنید.
چرخه عمر LoginComponent
چیست؟ یکی از دلایلی که شما به LoginComponent
نیاز دارید این است که شما باید همان نمونه LoginViewModel
را بین قطعات مرتبط با Login به اشتراک بگذارید. اما همچنین، هر زمان که یک جریان ورود جدید وجود داشته باشد، شما نمونه های مختلفی از LoginViewModel
را می خواهید. LoginActivity
طول عمر مناسب برای LoginComponent
است: برای هر فعالیت جدید، شما به یک نمونه جدید از LoginComponent
و قطعاتی نیاز دارید که بتواند از آن نمونه LoginComponent
استفاده کند.
از آنجایی که LoginComponent
به چرخه حیات LoginActivity
متصل است، باید به همان روشی که ارجاع به applicationComponent
را در کلاس Application
نگه داشتید، یک مرجع به کامپوننت در اکتیویتی نگه دارید. به این ترتیب، قطعات می توانند به آن دسترسی داشته باشند.
کاتلین
class LoginActivity: Activity() { // Reference to the Login graph lateinit var loginComponent: LoginComponent ... }
جاوا
public class LoginActivity extends Activity { // Reference to the Login graph LoginComponent loginComponent; ... }
توجه داشته باشید که متغیر loginComponent
با @Inject
حاشیه نویسی نشده است زیرا انتظار ندارید آن متغیر توسط Dagger ارائه شود.
میتوانید از ApplicationComponent
برای دریافت ارجاع به LoginComponent
و سپس تزریق LoginActivity
به صورت زیر استفاده کنید:
کاتلین
class LoginActivity: Activity() { // Reference to the Login graph lateinit var loginComponent: LoginComponent // Fields that need to be injected by the login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Creation of the login graph using the application graph loginComponent = (applicationContext as MyDaggerApplication) .appComponent.loginComponent().create() // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this) // Now loginViewModel is available super.onCreate(savedInstanceState) } }
جاوا
public class LoginActivity extends Activity { // Reference to the Login graph LoginComponent loginComponent; // Fields that need to be injected by the login graph @Inject LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { // Creation of the login graph using the application graph loginComponent = ((MyApplication) getApplicationContext()) .appComponent.loginComponent().create(); // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this); // Now loginViewModel is available super.onCreate(savedInstanceState); } }
LoginComponent
در متد onCreate()
اکتیویتی ایجاد می شود و زمانی که اکتیویتی از بین می رود بطور ضمنی از بین می رود.
LoginComponent
باید همیشه هر بار که درخواست می شود همان نمونه LoginViewModel
را ارائه دهد. شما می توانید با ایجاد یک محدوده حاشیه نویسی سفارشی و حاشیه نویسی هر دو LoginComponent
و LoginViewModel
با آن اطمینان حاصل کنید. توجه داشته باشید که نمی توانید از حاشیه نویسی @Singleton
استفاده کنید زیرا قبلاً توسط مؤلفه والد استفاده شده است و این موضوع را به یک برنامه کاربردی تبدیل می کند (نمونه ای منحصر به فرد برای کل برنامه). شما باید یک محدوده حاشیه نویسی متفاوت ایجاد کنید.
در این مورد، میتوانستید این محدوده را @LoginScope
نامیده باشید، اما این عمل خوبی نیست. نام حاشیه نویسی محدوده نباید واضح باشد به هدفی که انجام می دهد. در عوض، باید بسته به طول عمر آن نامگذاری شود زیرا حاشیه نویسی می تواند توسط مؤلفه های خواهر و برادر مانند RegistrationComponent
و SettingsComponent
دوباره استفاده شود. به همین دلیل است که باید آن را به جای @LoginScope
@ActivityScope
بنامید.
کاتلین
// Definition of a custom scope called ActivityScope @Scope @Retention(value = AnnotationRetention.RUNTIME) annotation class ActivityScope // Classes annotated with @ActivityScope are scoped to the graph and the same // instance of that type is provided every time the type is requested. @ActivityScope @Subcomponent interface LoginComponent { ... } // A unique instance of LoginViewModel is provided in Components // annotated with @ActivityScope @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
جاوا
// Definition of a custom scope called ActivityScope @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope {} // Classes annotated with @ActivityScope are scoped to the graph and the same // instance of that type is provided every time the type is requested. @ActivityScope @Subcomponent public interface LoginComponent { ... } // A unique instance of LoginViewModel is provided in Components // annotated with @ActivityScope @ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
حال، اگر دو قطعه داشتید که نیاز به LoginViewModel
دارند، هر دوی آنها با یک نمونه ارائه میشوند. برای مثال، اگر یک LoginUsernameFragment
و یک LoginPasswordFragment
دارید، باید توسط LoginComponent
تزریق شوند:
کاتلین
@ActivityScope @Subcomponent interface LoginComponent { @Subcomponent.Factory interface Factory { fun create(): LoginComponent } // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment // request injection from LoginComponent. The graph needs to satisfy // all the dependencies of the fields those classes are injecting fun inject(loginActivity: LoginActivity) fun inject(usernameFragment: LoginUsernameFragment) fun inject(passwordFragment: LoginPasswordFragment) }
جاوا
@ActivityScope @Subcomponent public interface LoginComponent { @Subcomponent.Factory interface Factory { LoginComponent create(); } // All LoginActivity, LoginUsernameFragment and LoginPasswordFragment // request injection from LoginComponent. The graph needs to satisfy // all the dependencies of the fields those classes are injecting void inject(LoginActivity loginActivity); void inject(LoginUsernameFragment loginUsernameFragment); void inject(LoginPasswordFragment loginPasswordFragment); }
کامپوننت ها به نمونه کامپوننتی که در شی LoginActivity
زندگی می کند دسترسی پیدا می کنند. کد مثال برای LoginUserNameFragment
در قطعه کد زیر ظاهر می شود:
کاتلین
class LoginUsernameFragment: Fragment() { // Fields that need to be injected by the login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onAttach(context: Context) { super.onAttach(context) // Obtaining the login graph from LoginActivity and instantiate // the @Inject fields with objects from the graph (activity as LoginActivity).loginComponent.inject(this) // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
جاوا
public class LoginUsernameFragment extends Fragment { // Fields that need to be injected by the login graph @Inject LoginViewModel loginViewModel; @Override public void onAttach(Context context) { super.onAttach(context); // Obtaining the login graph from LoginActivity and instantiate // the @Inject fields with objects from the graph ((LoginActivity) getActivity()).loginComponent.inject(this); // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
و همینطور برای LoginPasswordFragment
:
کاتلین
class LoginPasswordFragment: Fragment() { // Fields that need to be injected by the login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onAttach(context: Context) { super.onAttach(context) (activity as LoginActivity).loginComponent.inject(this) // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
جاوا
public class LoginPasswordFragment extends Fragment { // Fields that need to be injected by the login graph @Inject LoginViewModel loginViewModel; @Override public void onAttach(Context context) { super.onAttach(context); ((LoginActivity) getActivity()).loginComponent.inject(this); // Now you can access loginViewModel here and onCreateView too // (shared instance with the Activity and the other Fragment) } }
شکل 3 نشان می دهد که چگونه نمودار Dagger با زیر جزء جدید به نظر می رسد. کلاسهای دارای نقطه سفید ( UserRepository
، LoginRetrofitService
و LoginViewModel
) کلاسهایی هستند که یک نمونه منحصربهفرد برای اجزای مربوطه خود دارند.
بیایید بخش های نمودار را تجزیه کنیم:
NetworkModule
(و بنابراینLoginRetrofitService
) درApplicationComponent
گنجانده شده است زیرا شما آن را در کامپوننت مشخص کرده اید.UserRepository
درApplicationComponent
باقی می ماند زیرا محدوده آن بهApplicationComponent
است. اگر پروژه رشد کند، میخواهید نمونه مشابهی را در ویژگیهای مختلف به اشتراک بگذارید (مثلاً ثبت نام).از آنجایی که
UserRepository
بخشی ازApplicationComponent
است، وابستگیهای آن (یعنیUserLocalDataSource
وUserRemoteDataSource
) نیز باید در این مؤلفه باشد تا بتواند نمونههایی ازUserRepository
را ارائه دهد.LoginViewModel
درLoginComponent
گنجانده شده است زیرا فقط توسط کلاس های تزریق شده توسطLoginComponent
مورد نیاز است.LoginViewModel
درApplicationComponent
گنجانده نشده است زیرا هیچ وابستگی درApplicationComponent
بهLoginViewModel
نیاز ندارد.به طور مشابه، اگر محدوده
UserRepository
بهApplicationComponent
وارد نکرده بودید، Dagger به طور خودکارUserRepository
و وابستگی های آن را به عنوان بخشی ازLoginComponent
درج می کرد زیرا در حال حاضر تنها جایی است کهUserRepository
استفاده می شود.
جدای از محدوده بندی اشیاء در چرخه حیات متفاوت، ایجاد مولفه های فرعی روش خوبی برای کپسوله کردن بخش های مختلف برنامه شما از یکدیگر است .
ساختار برنامهتان برای ایجاد زیرگرافهای مختلف Dagger بسته به جریان برنامه، به برنامه کاربردیتر و مقیاسپذیرتر از نظر حافظه و زمان راهاندازی کمک میکند.
بهترین روش ها هنگام ساخت نمودار Dagger
هنگام ساخت نمودار Dagger برای برنامه شما:
هنگامی که یک کامپوننت را ایجاد می کنید، باید در نظر بگیرید که چه عنصری مسئول طول عمر آن جزء است. در این حالت، کلاس
Application
مسئولApplicationComponent
وLoginActivity
مسئولLoginComponent
است.از محدوده زمانی استفاده کنید که منطقی باشد. استفاده بیش از حد از محدوده می تواند تأثیر منفی بر عملکرد زمان اجرا برنامه شما داشته باشد: تا زمانی که کامپوننت در حافظه باشد، شی در حافظه است و دریافت یک شی با دامنه گران تر است. وقتی Dagger شی را ارائه می دهد، به جای ارائه دهنده نوع کارخانه از قفل
DoubleCheck
استفاده می کند.
آزمایش پروژه ای که از Dagger استفاده می کند
یکی از مزایای استفاده از چارچوبهای تزریق وابستگی مانند Dagger این است که تست کد شما را آسانتر میکند.
تست های واحد
لازم نیست از Dagger برای تست های واحد استفاده کنید. هنگام آزمایش کلاسی که از تزریق سازنده استفاده می کند، نیازی به استفاده از Dagger برای نمونه سازی آن کلاس ندارید. میتوانید مستقیماً سازنده آن را که در وابستگیهای جعلی یا ساختگی میگذرد، مستقیماً همانطور که اگر حاشیهنویسی نمیشد تماس بگیرید.
به عنوان مثال، هنگام آزمایش LoginViewModel
:
کاتلین
@ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... } class LoginViewModelTest { @Test fun `Happy path`() { // You don't need Dagger to create an instance of LoginViewModel // You can pass a fake or mock UserRepository val viewModel = LoginViewModel(fakeUserRepository) assertEquals(...) } }
جاوا
@ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } } public class LoginViewModelTest { @Test public void happyPath() { // You don't need Dagger to create an instance of LoginViewModel // You can pass a fake or mock UserRepository LoginViewModel viewModel = new LoginViewModel(fakeUserRepository); assertEquals(...); } }
تست های پایان به انتها
برای تست های یکپارچه سازی ، یک تمرین خوب این است که یک TestApplicationComponent
برای آزمایش ایجاد کنید. تولید و آزمایش از پیکربندی اجزای متفاوتی استفاده می کند .
این نیاز به طراحی قبلی بیشتر ماژول ها در برنامه شما دارد. مؤلفه آزمایشی مؤلفه تولید را گسترش می دهد و مجموعه متفاوتی از ماژول ها را نصب می کند.
کاتلین
// TestApplicationComponent extends from ApplicationComponent to have them both // with the same interface methods. You need to include the modules of the // component here as well, and you can replace the ones you want to override. // This sample uses FakeNetworkModule instead of NetworkModule @Singleton @Component(modules = [FakeNetworkModule::class, SubcomponentsModule::class]) interface TestApplicationComponent : ApplicationComponent { }
جاوا
// TestApplicationComponent extends from ApplicationComponent to have them both // with the same interface methods. You need to include the modules of the // Component here as well, and you can replace the ones you want to override. // This sample uses FakeNetworkModule instead of NetworkModule @Singleton @Component(modules = {FakeNetworkModule.class, SubcomponentsModule.class}) public interface TestApplicationComponent extends ApplicationComponent { }
FakeNetworkModule
یک پیاده سازی جعلی از NetworkModule
اصلی دارد. در آنجا می توانید نمونه های جعلی یا مسخره هر چیزی را که می خواهید جایگزین کنید ارائه دهید.
کاتلین
// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService // that you can use in your tests. @Module class FakeNetworkModule { @Provides fun provideLoginRetrofitService(): LoginRetrofitService { return FakeLoginService() } }
جاوا
// In the FakeNetworkModule, pass a fake implementation of LoginRetrofitService // that you can use in your tests. @Module public class FakeNetworkModule { @Provides public LoginRetrofitService provideLoginRetrofitService() { return new FakeLoginService(); } }
در آزمایشهای ادغام یا پایان به انتها، از TestApplication
استفاده میکنید که TestApplicationComponent
به جای ApplicationComponent
ایجاد میکند.
کاتلین
// Your test application needs an instance of the test graph class MyTestApplication: MyApplication() { override val appComponent = DaggerTestApplicationComponent.create() }
جاوا
// Your test application needs an instance of the test graph public class MyTestApplication extends MyApplication { ApplicationComponent appComponent = DaggerTestApplicationComponent.create(); }
سپس، این برنامه آزمایشی در یک TestRunner
سفارشی استفاده می شود که از آن برای اجرای تست های ابزار دقیق استفاده می کنید. برای اطلاعات بیشتر در این مورد، استفاده از خنجر را در نرم افزار کد برنامه Android خود بررسی کنید.
کار با ماژول های Dagger
ماژول های خنجر راهی برای کپسوله کردن نحوه ارائه اشیاء به روش معنایی هستند. شما می توانید ماژول ها را در کامپوننت ها قرار دهید اما می توانید ماژول ها را در ماژول های دیگر نیز قرار دهید. این قدرتمند است، اما به راحتی می توان از آن سوء استفاده کرد.
هنگامی که یک ماژول به یک جزء یا یک ماژول دیگر اضافه شد، از قبل در نمودار Dagger قرار دارد. Dagger می تواند آن اشیاء را در آن جزء فراهم کند. قبل از افزودن یک ماژول، بررسی کنید که آیا آن ماژول قبلاً بخشی از نمودار Dagger است و بررسی کنید که آیا قبلاً به مؤلفه اضافه شده است یا با کامپایل کردن پروژه و دیدن اینکه آیا Dagger می تواند وابستگی های مورد نیاز آن ماژول را پیدا کند.
رویه خوب حکم می کند که ماژول ها باید فقط یک بار در یک مؤلفه (خارج از موارد خاص استفاده پیشرفته Dagger) اعلام شوند.
فرض کنید نمودار خود را به این شکل پیکربندی کرده اید. ApplicationComponent
شامل Module1
و Module2
و Module1
شامل ModuleX
است.
کاتلین
@Component(modules = [Module1::class, Module2::class]) interface ApplicationComponent { ... } @Module(includes = [ModuleX::class]) class Module1 { ... } @Module class Module2 { ... }
جاوا
@Component(modules = {Module1.class, Module2.class}) public interface ApplicationComponent { ... } @Module(includes = {ModuleX.class}) public class Module1 { ... } @Module public class Module2 { ... }
اگر اکنون Module2
به کلاس های ارائه شده توسط ModuleX
بستگی دارد. یک روش بد گنجاندن ModuleX
در Module2
است زیرا ModuleX
دو بار در نمودار گنجانده شده است همانطور که در قطعه کد زیر مشاهده می شود:
کاتلین
// Bad practice: ModuleX is declared multiple times in this Dagger graph @Component(modules = [Module1::class, Module2::class]) interface ApplicationComponent { ... } @Module(includes = [ModuleX::class]) class Module1 { ... } @Module(includes = [ModuleX::class]) class Module2 { ... }
جاوا
// Bad practice: ModuleX is declared multiple times in this Dagger graph. @Component(modules = {Module1.class, Module2.class}) public interface ApplicationComponent { ... } @Module(includes = ModuleX.class) public class Module1 { ... } @Module(includes = ModuleX.class) public class Module2 { ... }
در عوض، شما باید یکی از موارد زیر را انجام دهید:
- ماژول ها را دوباره فاکتور کنید و ماژول مشترک را به کامپوننت استخراج کنید.
- یک ماژول جدید با اشیایی که هر دو ماژول به اشتراک می گذارند ایجاد کنید و آن را در جزء استخراج کنید.
عدم بازسازی به این روش منجر به ایجاد بسیاری از ماژولها میشود که بدون داشتن حس سازماندهی واضحی به یکدیگر متصل میشوند و دیدن اینکه هر وابستگی از کجا آمده است را دشوارتر میکند.
تمرین خوب (گزینه 1) : ModuleX یک بار در نمودار Dagger اعلام می شود.
کاتلین
@Component(modules = [Module1::class, Module2::class, ModuleX::class]) interface ApplicationComponent { ... } @Module class Module1 { ... } @Module class Module2 { ... }
جاوا
@Component(modules = {Module1.class, Module2.class, ModuleX.class}) public interface ApplicationComponent { ... } @Module public class Module1 { ... } @Module public class Module2 { ... }
تمرین خوب (گزینه 2) : وابستگی های رایج از Module1
و Module2
در ModuleX
به یک ماژول جدید به نام ModuleXCommon
که در جزء گنجانده شده است استخراج می شود. سپس دو ماژول دیگر به نامهای ModuleXWithModule1Dependencies
و ModuleXWithModule2Dependencies
با وابستگیهایی که مختص هر ماژول است ایجاد میشود. همه ماژول ها یک بار در نمودار Dagger اعلام می شوند.
کاتلین
@Component(modules = [Module1::class, Module2::class, ModuleXCommon::class]) interface ApplicationComponent { ... } @Module class ModuleXCommon { ... } @Module class ModuleXWithModule1SpecificDependencies { ... } @Module class ModuleXWithModule2SpecificDependencies { ... } @Module(includes = [ModuleXWithModule1SpecificDependencies::class]) class Module1 { ... } @Module(includes = [ModuleXWithModule2SpecificDependencies::class]) class Module2 { ... }
جاوا
@Component(modules = {Module1.class, Module2.class, ModuleXCommon.class}) public interface ApplicationComponent { ... } @Module public class ModuleXCommon { ... } @Module public class ModuleXWithModule1SpecificDependencies { ... } @Module public class ModuleXWithModule2SpecificDependencies { ... } @Module(includes = ModuleXWithModule1SpecificDependencies.class) public class Module1 { ... } @Module(includes = ModuleXWithModule2SpecificDependencies.class) public class Module2 { ... }
تزریق کمکی
تزریق کمکی یک الگوی DI است که برای ساخت یک شی استفاده میشود که در آن برخی از پارامترها ممکن است توسط چارچوب DI ارائه شوند و برخی دیگر باید در زمان ایجاد توسط کاربر ارسال شوند.
در اندروید، این الگو در صفحههای جزئیات رایج است که در آن شناسه عنصری که باید نشان داده شود فقط در زمان اجرا مشخص است، نه در زمان کامپایل که Dagger نمودار DI را تولید میکند. برای کسب اطلاعات بیشتر در مورد تزریق کمکی با Dagger، به مستندات Dagger مراجعه کنید.
نتیجه گیری
اگر قبلاً این کار را نکردهاید، بخش بهترین روشها را مرور کنید. برای مشاهده نحوه استفاده از Dagger در یک برنامه Android، به استفاده از Dagger در کد لبه برنامه Android مراجعه کنید.