پروژه ای با چندین ماژول Gradle به عنوان پروژه چند ماژول شناخته می شود. در یک پروژه چند ماژوله که به صورت یک APK منفرد بدون ماژول های ویژگی ارسال می شود، داشتن یک ماژول app
که می تواند به اکثر ماژول های پروژه شما بستگی داشته باشد و یک ماژول base
یا core
که بقیه ماژول ها معمولاً به آن وابسته هستند، معمول است. ماژول app
معمولاً شامل کلاس Application
شما است، در حالی که ماژول base
شامل تمام کلاسهای مشترک است که در همه ماژولهای پروژه شما به اشتراک گذاشته شده است.
ماژول app
مکان خوبی برای اعلام مؤلفه برنامه شما (به عنوان مثال، ApplicationComponent
در تصویر زیر) است که می تواند اشیایی را که سایر مؤلفه ها ممکن است به آن نیاز داشته باشند و همچنین تک تک برنامه شما ارائه دهد. به عنوان مثال، کلاس هایی مانند OkHttpClient
، تجزیه کننده های JSON، ابزارهای دسترسی برای پایگاه داده شما، یا اشیاء SharedPreferences
که ممکن است در ماژول core
تعریف شوند، توسط ApplicationComponent
تعریف شده در ماژول app
ارائه می شوند.
در ماژول app
، می توانید اجزای دیگری با طول عمر کمتر نیز داشته باشید. یک مثال می تواند یک UserComponent
با پیکربندی خاص کاربر (مانند یک UserSession
) پس از ورود به سیستم باشد.
در ماژول های مختلف پروژه خود، می توانید حداقل یک جزء فرعی را تعریف کنید که منطقی خاص برای آن ماژول داشته باشد، همانطور که در شکل 1 مشاهده می شود.
برای مثال، در یک ماژول login
، میتوانید یک LoginComponent
با حاشیهنویسی @ModuleScope
سفارشی داشته باشید که میتواند اشیاء مشترک با آن ویژگی مانند LoginRepository
را ارائه دهد. در داخل آن ماژول، میتوانید مؤلفههای دیگری را نیز داشته باشید که به یک LoginComponent
با دامنه سفارشی متفاوت بستگی دارد، به عنوان مثال @FeatureScope
برای یک LoginActivityComponent
یا یک TermsAndConditionsComponent
که در آن میتوانید منطق ویژگیهای خاص بیشتری مانند اشیاء ViewModel
را در بر بگیرید.
برای سایر ماژولها مانند Registration
، تنظیمات مشابهی خواهید داشت.
یک قانون کلی برای یک پروژه چند ماژول این است که ماژول های هم سطح نباید به یکدیگر وابسته باشند. اگر چنین کردند، در نظر بگیرید که آیا منطق مشترک (وابستگی های بین آنها) باید بخشی از ماژول والد باشد یا خیر. اگر چنین است، برای انتقال کلاس ها به ماژول والد، Refactor کنید. اگر نه، یک ماژول جدید ایجاد کنید که ماژول والد را گسترش دهد و هر دو ماژول اصلی ماژول جدید را گسترش دهند.
به عنوان بهترین روش، معمولاً در موارد زیر یک جزء در یک ماژول ایجاد می کنید:
مانند
LoginActivityComponent
باید تزریق فیلد را انجام دهید.شما باید مانند
LoginComponent
اشیاء را محدوده بندی کنید.
اگر هیچ یک از این موارد صدق نمی کند و باید به Dagger بگویید چگونه اشیاء را از آن ماژول ارائه کند، اگر تزریق ساخت برای آن کلاس ها امکان پذیر نیست، یک ماژول Dagger را با متدهای @Provides
یا @Binds
ایجاد و در معرض دید قرار دهید.
پیاده سازی با اجزای فرعی Dagger
صفحه سند استفاده از خنجر در برنامههای اندروید نحوه ایجاد و استفاده از اجزای فرعی را پوشش میدهد. با این حال، نمی توانید از همان کد استفاده کنید زیرا ماژول های ویژگی در مورد ماژول app
اطلاعاتی ندارند. به عنوان مثال، اگر به یک جریان ورود معمولی و کدی که در صفحه قبل داریم فکر می کنید، دیگر کامپایل نمی شود:
کاتلین
class LoginActivity: Activity() { ... 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) ... } }
جاوا
public class LoginActivity extends Activity { ... @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); ... } }
دلیل آن این است که ماژول login
درباره MyApplication
یا appComponent
اطلاعی ندارد. برای اینکه کار کند، باید یک رابط در ماژول ویژگی تعریف کنید که FeatureComponent
را ارائه کند که MyApplication
باید پیادهسازی کند.
در مثال زیر، می توانید یک رابط LoginComponentProvider
تعریف کنید که یک LoginComponent
در ماژول login
برای جریان ورود به سیستم ارائه می دهد:
کاتلین
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
جاوا
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
اکنون، LoginActivity
از آن رابط به جای قطعه کد تعریف شده در بالا استفاده می کند:
کاتلین
class LoginActivity: Activity() { ... override fun onCreate(savedInstanceState: Bundle?) { loginComponent = (applicationContext as LoginComponentProvider) .provideLoginComponent() loginComponent.inject(this) ... } }
جاوا
public class LoginActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { loginComponent = ((LoginComponentProvider) getApplicationContext()) .provideLoginComponent(); loginComponent.inject(this); ... } }
اکنون، MyApplication
باید آن رابط را پیاده سازی کند و روش های مورد نیاز را پیاده سازی کند:
کاتلین
class MyApplication: Application(), LoginComponentProvider { // Reference to the application graph that is used across the whole app val appComponent = DaggerApplicationComponent.create() override fun provideLoginComponent(): LoginComponent { return appComponent.loginComponent().create() } }
جاوا
public class MyApplication extends Application implements LoginComponentProvider { // Reference to the application graph that is used across the whole app ApplicationComponent appComponent = DaggerApplicationComponent.create(); @Override public LoginComponent provideLoginComponent() { return appComponent.loginComponent.create(); } }
به این ترتیب میتوانید از اجزای فرعی Dagger در یک پروژه چند ماژول استفاده کنید. در مورد ماژول های ویژگی، راه حل به دلیل نحوه وابستگی ماژول ها به یکدیگر متفاوت است.
وابستگی های مؤلفه با ماژول های ویژگی
با ماژولهای ویژگی ، نحوه وابستگی ماژولها به یکدیگر معکوس میشود. به جای ماژول app
شامل ماژول های ویژگی، ماژول های ویژگی به ماژول app
بستگی دارند. شکل 2 را برای نمایش نحوه ساختار ماژول ها ببینید.
در Dagger، اجزا باید در مورد اجزای فرعی خود بدانند. این اطلاعات در یک ماژول Dagger اضافه شده به مؤلفه اصلی (مانند ماژول SubcomponentsModule
در استفاده از Dagger در برنامههای Android ) گنجانده شده است.
متأسفانه، با وابستگی معکوس بین برنامه و ماژول ویژگی، جزء فرعی از ماژول app
قابل مشاهده نیست زیرا در مسیر ساخت قرار ندارد. به عنوان مثال، یک LoginComponent
تعریف شده در یک ماژول ویژگی login
نمی تواند یک جزء فرعی از ApplicationComponent
تعریف شده در ماژول app
باشد.
Dagger مکانیزمی به نام وابستگی کامپوننت دارد که می توانید برای حل این مشکل از آن استفاده کنید. به جای اینکه جزء فرزند جزء فرعی مؤلفه والد باشد، مؤلفه فرزند به مؤلفه والد وابسته است. با آن، هیچ رابطه والدین و فرزندی وجود ندارد. اکنون مولفه ها برای دریافت وابستگی های خاص به دیگران وابسته هستند. کامپوننت ها باید انواع را از نمودار در معرض دید قرار دهند تا اجزای وابسته آن ها را مصرف کنند.
به عنوان مثال: یک ماژول ویژگی به نام login
می خواهد یک LoginComponent
بسازد که به AppComponent
موجود در ماژول app
Gradle بستگی دارد.
در زیر تعاریفی برای کلاس ها و AppComponent
که بخشی از ماژول app
Gradle هستند آورده شده است:
کاتلین
// UserRepository's dependencies class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... } // UserRepository is scoped to AppComponent @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } @Singleton @Component interface AppComponent { ... }
جاوا
// UserRepository's dependencies public class UserLocalDataSource { @Inject public UserLocalDataSource() {} } public class UserRemoteDataSource { @Inject public UserRemoteDataSource() { } } // UserRepository is scoped to AppComponent @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; } } @Singleton @Component public interface ApplicationComponent { ... }
در ماژول gradle login
خود که شامل ماژول gradle app
است، یک LoginActivity
دارید که برای تزریق به یک نمونه LoginViewModel
نیاز دارد:
کاتلین
// LoginViewModel depends on UserRepository that is scoped to AppComponent class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
جاوا
// LoginViewModel depends on UserRepository that is scoped to AppComponent public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
LoginViewModel
وابستگی به UserRepository
دارد که در دسترس است و در محدوده AppComponent
قرار دارد. بیایید یک LoginComponent
ایجاد کنیم که برای تزریق LoginActivity
به AppComponent
بستگی دارد:
کاتلین
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = [AppComponent::class]) interface LoginComponent { fun inject(activity: LoginActivity) }
جاوا
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = AppComponent.class) public interface LoginComponent { void inject(LoginActivity loginActivity); }
LoginComponent
یک وابستگی به AppComponent
را با افزودن آن به پارامتر وابستگی حاشیه نویسی مؤلفه مشخص می کند. از آنجا که LoginActivity
توسط Dagger تزریق می شود، متد inject()
به اینترفیس اضافه کنید.
هنگام ایجاد یک LoginComponent
، یک نمونه از AppComponent
باید ارسال شود. برای انجام آن از کارخانه مؤلفه استفاده کنید:
کاتلین
@Component(dependencies = [AppComponent::class]) interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent fun create(appComponent: AppComponent): LoginComponent } fun inject(activity: LoginActivity) }
جاوا
@Component(dependencies = AppComponent.class) public interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent LoginComponent create(AppComponent appComponent); } void inject(LoginActivity loginActivity); }
اکنون LoginActivity
می تواند یک نمونه از LoginComponent
ایجاد کند و متد inject()
را فراخوانی کند.
کاتلین
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Gets appComponent from MyApplication available in the base Gradle module val appComponent = (applicationContext as MyApplication).appComponent // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this) super.onCreate(savedInstanceState) // Now you can access loginViewModel } }
جاوا
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Gets appComponent from MyApplication available in the base Gradle module AppComponent appComponent = ((MyApplication) getApplicationContext()).appComponent; // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this); // Now you can access loginViewModel } }
LoginViewModel
به UserRepository
بستگی دارد. و برای اینکه LoginComponent
بتواند از AppComponent
به آن دسترسی داشته باشد، AppComponent
باید آن را در رابط کاربری خود نشان دهد:
کاتلین
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
جاوا
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
قوانین محدوده با اجزای وابسته به همان روشی که با اجزای فرعی کار می کنند. از آنجا که LoginComponent
از نمونه ای از AppComponent
استفاده می کند، نمی توانند از حاشیه نویسی یکسان استفاده کنند.
اگر میخواهید LoginViewModel
به LoginComponent
تغییر دهید، همانطور که قبلاً با استفاده از حاشیهنویسی سفارشی @ActivityScope
انجام دادید، این کار را انجام میدهید.
کاتلین
@ActivityScope @Component(dependencies = [AppComponent::class]) interface LoginComponent { ... } @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
جاوا
@ActivityScope @Component(dependencies = AppComponent.class) public interface LoginComponent { ... } @ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
بهترین شیوه ها
ApplicationComponent
باید همیشه در ماژولapp
باشد.اگر نیاز به انجام تزریق فیلد در آن ماژول دارید یا نیاز دارید که اشیاء را برای یک جریان خاص از برنامه خود محدوده بندی کنید، اجزای Dagger را در ماژول ها ایجاد کنید.
برای ماژولهای Gradle که قرار است ابزارهای کمکی یا کمکی باشند و نیازی به ساختن نمودار ندارند (به همین دلیل است که به یک مؤلفه Dagger نیاز دارید)، ماژولهای عمومی Dagger را با متدهای @Provides و @Binds از کلاسهایی که انجام میدهند ایجاد کنید و در معرض دید قرار دهید. از تزریق سازنده پشتیبانی نمی کند.
برای استفاده از Dagger در یک برنامه Android با ماژول های ویژگی، از وابستگی های مؤلفه استفاده کنید تا بتوانید به وابستگی های ارائه شده توسط
ApplicationComponent
تعریف شده در ماژولapp
دسترسی پیدا کنید.