يُعرف المشروع الذي يحتوي على وحدات 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
المتاحة في
وحدة Gradle على app
.
في ما يلي تعريفات للفئات و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 { ... }
في وحدة الدرجة login
التي تتضمّن وحدة الدرجة 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 و @الإعمل لهما من هذه الفئات التي لا تتيح حقن دالة الإنشاء.
لاستخدام Dagger في تطبيق Android يتضمّن وحدات ميزات، استخدِم تبعيات المكوّنات لتتمكّن من الوصول إلى العناصر الاعتمادية التي توفّرها السمة
ApplicationComponent
المحدّدة في وحدةapp
.