يُعرف المشروع الذي يحتوي على وحدات 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
، سيكون لديك إعداد مشابه.
تتمثل القاعدة العامة لمشروع متعدد الوحدات في أن الوحدات من نفس المستوى أن يعتمدوا على بعضهم البعض. إذا كان الأمر كذلك، ففكر فيما إذا كان هذا المنطق المشترك (التبعيات بينها) يجب أن تكون جزءًا من الوحدة الأصلية. إذا كان الأمر كذلك، وإعادة البناء لنقل الفئات إلى الوحدة الأصلية؛ وإذا لم يكن الأمر كذلك، فأنشئ وحدة جديدة توسع الوحدة الأصلية وتضم كلتا الوحدتين الأصليتين وحدة جديدة.
كأفضل ممارسة، يمكنك بشكل عام إنشاء مكون في في الحالات التالية:
يجب إدخال الحقل، كما هو الحال في
LoginActivityComponent
.يجب تحديد نطاق العناصر، كما هو الحال في
LoginComponent
.
إذا لم تنطبق أي من هذه الحالات وعليك إخبار Dagger بكيفية تقديم
من تلك الوحدة، يمكنك إنشاء وعرض وحدة Dagger باستخدام @Provides
أو
@Binds
طريقة إذا لم يكن من الممكن إجراء حقن مواد البناء لهذه الفئات.
التنفيذ باستخدام مكوّنات Dagger الفرعية
تتناول صفحة المستندات استخدام Dagger في تطبيقات Android طريقة إنشاء واستخدام
المكونات الفرعية. ومع ذلك، لا يمكنك استخدام الرمز نفسه لأنه
لا تعرف وحدات الميزات عن الوحدة app
. على سبيل المثال، إذا كنت تعتقد
حول تدفق تسجيل الدخول النموذجي والرمز الذي لدينا في الصفحة السابقة، فلن يكون
تجميع المزيد:
Kotlin
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) ... } }
Java
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
لتدفق تسجيل الدخول:
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
وبالتالي، سيستخدم LoginActivity
تلك الواجهة بدلاً من مقتطف الرمز.
المحدد أعلاه:
Kotlin
class LoginActivity: Activity() { ... override fun onCreate(savedInstanceState: Bundle?) { loginComponent = (applicationContext as LoginComponentProvider) .provideLoginComponent() loginComponent.inject(this) ... } }
Java
public class LoginActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { loginComponent = ((LoginComponentProvider) getApplicationContext()) .provideLoginComponent(); loginComponent.inject(this); ... } }
والآن، يحتاج MyApplication
إلى تنفيذ هذه الواجهة وتنفيذ
الطرق المطلوبة:
Kotlin
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() } }
Java
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
التي تشكّل جزءًا من
وحدة Gradle app
:
Kotlin
// 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 { ... }
Java
// 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
لإدخاله:
Kotlin
// LoginViewModel depends on UserRepository that is scoped to AppComponent class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
// 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
تعتمد على
AppComponent
لإدخال LoginActivity
:
Kotlin
// 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) }
Java
// 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
.
وفاته. استخدم مصنع المكونات للقيام بذلك:
Kotlin
@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) }
Java
@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()
Kotlin
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 } }
Java
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
، يجب عرضه في
واجهته:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
تعمل قواعد تحديد النطاق ذات المكونات التابعة بنفس الطريقة التي تعمل بها
المكونات الفرعية. بما أنّ LoginComponent
يستخدم مثيلاً من AppComponent
،
لا يمكنهم استخدام التعليق التوضيحي نفسه للنطاق.
إذا أردت تحديد النطاق من LoginViewModel
إلى LoginComponent
، يمكنك اتّباع الخطوات التالية:
سبق أن استخدمت تعليق @ActivityScope
المخصّص
Kotlin
@ActivityScope @Component(dependencies = [AppComponent::class]) interface LoginComponent { ... } @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
@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
.