توضّح صفحة أساسيات Dagger كيف يمكن لأداة Dagger مساعدتك في برمجة التبعية. حقن في التطبيق. باستخدام Dagger، لن تضطر إلى كتابة مهام الرمز النموذجي المعرّض للخطأ.
ملخّص أفضل الممارسات
- استخدام حقن الدالة الإنشائية مع
@Inject
لإضافة أنواع إلى Dagger الرسم البياني كلما كان ذلك ممكنًا. بينما لا يكون:- استخدِم
@Binds
لإعلام Dagger بعملية التنفيذ التي يجب أن تتضمّنها الواجهة. - استخدِم "
@Provides
" لإخبار Dagger بكيفية تقديم الصفوف التي يستفيد منها مشروعك. لا يمتلكه.
- استخدِم
- يجب تعريف الوحدات مرة واحدة فقط في المكوِّن.
- قم بتسمية التعليقات التوضيحية للنطاق بناءً على فترة الإنشاء
استخدام التعليقات التوضيحية. تتضمن الأمثلة
@ApplicationScope
و@LoggedUserScope
و@ActivityScope
.
إضافة التبعيات
لاستخدام Dagger في مشروعك، أضف هذه التبعيات إلى تطبيقك في
ملف build.gradle
. يمكنك العثور على أحدث إصدار من Dagger.
في مشروع GitHub
Kotlin
plugins { id 'kotlin-kapt' } dependencies { implementation 'com.google.dagger:dagger:2.x' kapt 'com.google.dagger:dagger-compiler:2.x' }
Java
dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }
تطبيق Dagger في نظام Android
فكر في مثال على تطبيق Android يتضمن الرسم البياني للتبعية من الشكل 1.
![يعتمد نشاط تسجيل الدخول على LoginViewModel، الذي يعتمد على UserRepository،
والذي يعتمد على UserLocalDataSource وUserRemoteDataSource اللذين
يعتمد على عملية التحديث.](https://developer.android.com/static/images/training/dependency-injection/4-application-graph.png?authuser=9&hl=ar)
الشكل 1. الرسم البياني التبعية للمثال رقم الاعتماد
في نظام Android، يمكنك عادةً إنشاء رسم بياني الخنجر المتوفّر في تطبيقك.
لأنك تريد أن يكون هناك مثيل للرسم البياني في الذاكرة طالما
تطبيقك قيد التشغيل. بهذه الطريقة، يتم إرفاق الرسم البياني بدورة حياة التطبيق. في بعض
من الحالات، قد ترغب أيضًا في توفير سياق التطبيق في
الرسم البياني. ولإجراء ذلك، يجب أيضًا أن يكون الرسم البياني في
صف واحد (Application
). من مزايا ذلك
هو أن يكون الرسم البياني متوفرًا لفئات إطار عمل Android الأخرى.
بالإضافة إلى ذلك، يبسّط الاختبار من خلال السماح لك باستخدام
صف واحد (Application
) في الاختبارات.
ولأنّ الواجهة التي تُنشئ الرسم البياني تتضمّن تعليقات توضيحية باستخدام @Component
،
يمكنك تسميته ApplicationComponent
أو ApplicationGraph
. عادةً ما تحتفظ
تمثّل هذه السمة مثيلاً لهذا المكوِّن في فئة Application
المخصّصة وتسمّيه
في كل مرة تحتاج فيها إلى الرسم البياني للتطبيق، كما هو موضح في الرمز التالي
snippet:
Kotlin
// 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() }
Java
// 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(); }
وذلك لأنّ بعض فئات أطر عمل Android، مثل الأنشطة وأجزاء
التي ينشئها النظام، فلن يستطيع Dagger إنشاؤها نيابةً عنك. للأنشطة
وعلى وجه التحديد، يجب أن يتدخل أي رمز إعداد في طريقة onCreate()
.
وهذا يعني أنه لا يمكنك استخدام التعليق التوضيحي @Inject
في الدالة الإنشائية
(إدخال الدالة الإنشائية) كما فعلت في الأمثلة السابقة. بدلاً من ذلك،
عليك استخدام حقن الحقل.
بدلاً من إنشاء التبعيات التي يتطلبها نشاط في onCreate()
فأنت تريد أن يقوم Dagger بملء هذه التبعيات نيابة عنك. للحقل
مباشرةً، يمكنك بدلاً من ذلك تطبيق التعليق التوضيحي @Inject
على الحقول التي
التي نريد الحصول عليها من الرسم البياني لـ Dagger.
Kotlin
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject lateinit var loginViewModel: LoginViewModel }
Java
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject LoginViewModel loginViewModel; }
للتبسيط، إنّ LoginViewModel
ليس أحد مكونات Android الهندسية.
ViewModel؛ إنها مجرد فئة عادية تعمل كـ ViewModel.
لمزيد من المعلومات حول كيفية إدخال هذه الفئات، راجع الرمز
في تنفيذ Dagger من Android Blueprints الرسمي، في
فرع dev-dagger
من بين الاعتبارات التي يضعها تطبيق Dagger أنّه لا يمكن أن تكون الحقول التي تم إدخالها خاصة. ويجب أن يكون لها مستوى رؤية خاص بالحزمة على الأقل كما في التعليمة البرمجية السابقة.
إدخال الأنشطة
يحتاج Dagger إلى معرفة أن على LoginActivity
الوصول إلى الرسم البياني
تقديم ViewModel
المطلوبة. في صفحة أساسيات Dagger، استخدمت
واجهة @Component
للحصول على عناصر من الرسم البياني
من خلال عرض الدوال بنوع الإرجاع لما تريد الحصول عليه من
الرسم البياني. في هذه الحالة، تحتاج إلى إخبار Dagger بشأن أحد الكائنات (LoginActivity
).
في هذه الحالة) يتطلب إدخال تبعية. للقيام بذلك، تعرض
يشير ذلك المصطلح إلى دالة تتعامل مع الكائن الذي يطلب الإدخال كمَعلمة.
Kotlin
@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) }
Java
@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()
، مع المرور في مثيل
النشاط الذي يطلب الحقن.
عند استخدام الأنشطة، أدخِل Dagger في
طريقة onCreate()
للنشاط قبل الاتصال بـ super.onCreate()
لتجنُّب المشاكل
من خلال استعادة الأجزاء. أثناء مرحلة الاستعادة في super.onCreate()
،
يُرفق أي نشاط الأجزاء التي قد ترغب في الوصول إلى روابط الأنشطة.
عند استخدام الأجزاء، أدخِل Dagger في onAttach()
للجزء.
. وفي هذه الحالة، يمكن إجراء ذلك قبل طلب الرقم super.onAttach()
أو بعده.
Kotlin
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 ) { ... }
Java
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 بكيفية توفير بقية التبعيات لبناء الرسم البياني:
Kotlin
class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor( private val loginService: LoginRetrofitService ) { ... }
Java
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; } }
وحدات Dagger
في هذا المثال، أنت تستخدم مكتبة الشبكات إعادة التعديل.
تعتمد الدالة UserRemoteDataSource
على LoginRetrofitService
. ومع ذلك،
تختلف طريقة إنشاء مثيل LoginRetrofitService
عن
التي كنت تفعلها حتى الآن. إنه ليس مثيلاً للفئة؛ إنها نتيجة
استدعاء Retrofit.Builder()
وتمرير معلَمات مختلفة لضبطها
خدمة تسجيل الدخول.
بخلاف التعليق التوضيحي @Inject
، هناك طريقة أخرى لإخبار Dagger بكيفية تنفيذ
لتقديم مثيل لفئة: المعلومات داخل وحدات Dagger. خنجر
هي فئة تمت إضافة تعليقات توضيحية إليها باستخدام @Module
. هناك، يمكنك تحديد
التابعة مع التعليق التوضيحي @Provides
.
Kotlin
// @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) } }
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
. مثلاً:
Kotlin
@Module class NetworkModule { // Hypothetical dependency on LoginRetrofitService @Provides fun provideLoginRetrofitService( okHttpClient: OkHttpClient ): LoginRetrofitService { ... } }
Java
@Module public class NetworkModule { @Provides public LoginRetrofitService provideLoginRetrofitService(OkHttpClient okHttpClient) { ... } }
حتى يتعرف الرسم البياني لـ Dagger على هذه الوحدة، يجب عليك إضافتها إلى
واجهة @Component
على النحو التالي:
Kotlin
// The "modules" attribute in the @Component annotation tells Dagger what Modules // to include when building the graph @Component(modules = [NetworkModule::class]) interface ApplicationComponent { ... }
Java
// 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 في المثال الآن:
![مخطّط بياني للرسم البياني لتبعية تسجيل الدخول](https://developer.android.com/static/images/training/dependency-injection/4-graph-login.png?authuser=9&hl=ar)
الشكل 2. تمثيل الرسم البياني باستخدام
يتم حقن LoginActivity
من خلال Dagger.
ونقطة الدخول إلى الرسم البياني هي LoginActivity
. لأنّ LoginActivity
يُدخِل البيانات
LoginViewModel
، ينشئ Dagger رسمًا بيانيًا يعرف كيفية تقديم مثال
LoginViewModel
، وبشكل متكرر، من تبعياتها. يعرف Dagger كيفية
إجراء ذلك بسبب تعليق @Inject
التوضيحي على الصفوف الدالة الإنشائية.
يتوفّر داخل ApplicationComponent
الذي أنشأه Dagger نوع مصنع.
للحصول على مثيلات لجميع الفئات التي يعرف كيفية تقديمها. في هذه الدورة،
على سبيل المثال، يفوض Dagger إلى NetworkModule
المضمنة في
ApplicationComponent
للحصول على مثيل LoginRetrofitService
.
نطاقات Dagger
تم ذكر النطاقات في صفحة أساسيات Dagger كوسيلة للحصول على مثيل فريد من نوع ما في المكون. هذا هو ما يعنيه تحديد نطاق نوع لدورة حياة المكون.
قد تحتاج إلى استخدام "UserRepository
" في ميزات أخرى داخل التطبيق
قد لا ترغب في إنشاء كائن جديد في كل مرة تحتاج فيها إليه، فيمكنك تعيين
كمثيل فريد للتطبيق بالكامل. والأمر نفسه بالنسبة إلى
LoginRetrofitService
: قد يكون إنشاء المحتوى مكلفًا، وتريد
مثيل فريد من ذلك الكائن لإعادة استخدامه. يمكن أن يؤدي إنشاء مثيل
إنّ قيمة "UserRemoteDataSource
" ليست باهظة الثمن، لذا يُرجى تحديد نطاقها
ليست هناك حاجة إلى دورة حياة المكون.
@Singleton
هو التعليق التوضيحي الوحيد الذي يأتي مع النطاق.
حزمة javax.inject
. يمكنك استخدامه لإضافة تعليقات توضيحية إلى "ApplicationComponent
".
والكائنات التي تريد إعادة استخدامها في التطبيق بأكمله.
Kotlin
@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 { ... } }
Java
@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() { ... } }
احرِص على عدم التعرُّف على تسريب الذاكرة عند تطبيق النطاقات على العناصر. بالنسبة
ما دام المكوِّن الذي تم تحديد نطاقه في الذاكرة، يكون العنصر الذي تم إنشاؤه في الذاكرة
أيضًا. لأنّ ApplicationComponent
يتم إنشاؤه عند تشغيل التطبيق (في
الفئة Application
)، يتم إتلافها عند تلف التطبيق. وبالتالي،
يظل المثيل الفريد لـ UserRepository
في الذاكرة دائمًا حتى
تم إتلاف التطبيق.
مكوّنات Dagger الفرعية
إذا كان مسار تسجيل الدخول (الذي يديره LoginActivity
واحد) يتألف من عدة خيارات
يجب إعادة استخدام مثيل LoginViewModel
نفسه في جميع
الأجزاء. لا يمكن لـ "@Singleton
" إضافة تعليقات توضيحية إلى LoginViewModel
لإعادة استخدام المثيل.
للأسباب التالية:
وقد يبقى مثيل
LoginViewModel
في الذاكرة بعد أن يصبح التدفق منتهٍ.تحتاج إلى مثيل مختلف من
LoginViewModel
لكل مسار تسجيل دخول. على سبيل المثال، إذا سجّل المستخدم خروجه، فأنت تريد مثيلاً مختلفًا منLoginViewModel
، بدلاً من المثيل نفسه الذي سجَّله المستخدم الدخول إلى للمرة الأولى.
لتضمين LoginViewModel
في دورة حياة LoginActivity
، عليك إنشاء
مكون جديد (رسم بياني فرعي جديد) لتدفق تسجيل الدخول ونطاق جديد.
لننشئ رسمًا بيانيًا خاصًا بتدفق تسجيل الدخول.
Kotlin
@Component interface LoginComponent {}
Java
@Component public interface LoginComponent { }
من المفترض أن يحصل LoginActivity
الآن على حُقن من LoginComponent
.
لديه إعدادات خاصة بتسجيل الدخول. وهذا يزيل المسؤولية عن إدخال
LoginActivity
من الصف ApplicationComponent
.
Kotlin
@Component interface LoginComponent { fun inject(activity: LoginActivity) }
Java
@Component public interface LoginComponent { void inject(LoginActivity loginActivity); }
يجب أن يكون بإمكان "LoginComponent
" الوصول إلى العناصر من "ApplicationComponent
"
لأن LoginViewModel
يعتمد على UserRepository
. طريقة إخبار Dagger أنه
فإنك تريد استخدام مكون جديد لجزء من مكون آخر
المكوّنات الفرعية لـ Dagger: يجب أن يكون المكون الجديد مكونًا فرعيًا
يحتوي على موارد مشتركة.
المكوّنات الفرعية هي مكونات ترث وتوسِّع الرسم البياني للكائن المكون الأصلي. وبالتالي، فإن جميع الكائنات المتوفرة في المكون الأصلي المقدمة في المكون الفرعي أيضًا. وبهذه الطريقة، يمكن استخدام كائن من مكوّن فرعي تعتمد على كائن يوفره المكون الرئيسي.
لإنشاء مثيلات للمكوّنات الفرعية، يجب توفُّر مثيل للمكوّنات الرئيسية المكون. ومن ثم، فإن الكائنات التي يقدمها المكون الرئيسي إلى لا يزال مكونًا فرعيًا محددًا للمكون الرئيسي.
في المثال، يجب تحديد LoginComponent
كمكوّن فرعي من
ApplicationComponent
لإجراء ذلك، أضِف تعليقات توضيحية إلى "LoginComponent
" باستخدام
@Subcomponent
:
Kotlin
// @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) }
Java
// @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
.
Kotlin
@Subcomponent interface LoginComponent { // Factory that is used to create instances of this subcomponent @Subcomponent.Factory interface Factory { fun create(): LoginComponent } fun inject(loginActivity: LoginActivity) }
Java
@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
للتعليق التوضيحي.Kotlin
// 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 {}
Java
// 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
:Kotlin
// Including SubcomponentsModule, tell ApplicationComponent that // LoginComponent is its subcomponent. @Singleton @Component(modules = [NetworkModule::class, SubcomponentsModule::class]) interface ApplicationComponent { }
Java
// 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
في :Kotlin
@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 }
Java
@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
بين الأجزاء المتعلقة بتسجيل الدخول. ولكنك أيضًا تريد نهجًا مختلفًا
LoginViewModel
كلما كانت هناك عملية تسجيل دخول جديدة. LoginActivity
هي العمر المناسب لمدة LoginComponent
: ستحتاج إلى ما يلي مقابل كل نشاط جديد
مثيل جديد لـ LoginComponent
وأجزاء يمكنها استخدام مثيل
LoginComponent
بما أنّ السمة LoginComponent
مرتبطة بدورة حياة LoginActivity
، يجب إجراء ما يلي:
احتفظ بإشارة إلى المكون في النشاط بنفس الطريقة التي احتفظت بها
يشير إلى applicationComponent
في الفئة Application
. بهذه الطريقة،
للأجزاء الوصول إليها.
Kotlin
class LoginActivity: Activity() { // Reference to the Login graph lateinit var loginComponent: LoginComponent ... }
Java
public class LoginActivity extends Activity { // Reference to the Login graph LoginComponent loginComponent; ... }
يُرجى ملاحظة أنّ المتغيّر loginComponent
ليس له تعليقات توضيحية باستخدام @Inject
.
لأنك لا تتوقع أن يوفر Dagger هذا المتغير.
يمكنك استخدام ApplicationComponent
للحصول على مرجع إلى LoginComponent
.
ثم أدخِل LoginActivity
على النحو التالي:
Kotlin
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) } }
Java
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
. لهذا السبب، يجب تسميته @ActivityScope
بدلاً من ذلك.
من @LoginScope
.
Kotlin
// 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 ) { ... }
Java
// 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
:
Kotlin
@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) }
Java
@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
في
مقتطف الرمز التالي:
Kotlin
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) } }
Java
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
:
Kotlin
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) } }
Java
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 كيف يبدو الرسم البياني الخنجر مع المكون الفرعي الجديد. الصفوف
مع نقطة بيضاء (UserRepository
وLoginRetrofitService
وLoginViewModel
)
هي تلك التي تحتوي على مثيل فريد محدد بمكوناتها الخاصة.
![الرسم البياني للتطبيق بعد إضافة المكوّن الفرعي الأخير](https://developer.android.com/static/images/training/dependency-injection/4-graph-subcomponent.png?authuser=9&hl=ar)
الشكل 3. تمثيل الرسم البياني الذي أنشأته لمثال تطبيق Android
لنحلل أجزاء الرسم البياني:
يتم تضمين
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
:
Kotlin
@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(...) } }
Java
@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
مخصّص للاختبار.
يستخدم الإنتاج والاختبار إعدادات مكوّنات مختلفة.
يتطلّب ذلك المزيد من التصميم المسبق للوحدات في البداية. تطبيقك. يقوم مكون الاختبار بتوسيع مكون الإنتاج تقوم بتثبيت مجموعة مختلفة من الوحدات.
Kotlin
// 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 { }
Java
// 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
الأصلي.
وهناك يمكنك تقديم مثيلات أو نماذج زائفة لأي شيء تريد استبداله.
Kotlin
// 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() } }
Java
// 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
.
Kotlin
// Your test application needs an instance of the test graph class MyTestApplication: MyApplication() { override val appComponent = DaggerTestApplicationComponent.create() }
Java
// Your test application needs an instance of the test graph public class MyTestApplication extends MyApplication { ApplicationComponent appComponent = DaggerTestApplicationComponent.create(); }
بعد ذلك، سيتم استخدام هذا التطبيق الاختباري في TestRunner
مخصّص ستستخدمه.
إجراء اختبارات الأجهزة. لمزيد من المعلومات حول هذا الأمر، اطلع على استخدام
Dagger في الدرس التطبيقي حول ترميز تطبيق Android.
استخدام وحدات Dagger
تشكّل وحدات Dagger طريقة لتغليف كيفية توفير الكائنات بطريقة دلالية. نفسها. يمكنك تضمين وحدات في المكوّنات، ولكن يمكنك أيضًا تضمين الوحدات. داخل وحدات أخرى. هذه الأداة قوية، ولكن يمكن إساءة استخدامها بسهولة.
وبمجرد إضافة وحدة إما إلى مكون أو وحدة أخرى، يتم استخدامها بالفعل في الرسم البياني لـ Dagger؛ يمكن أن يوفر Dagger هذه الكائنات في هذا المكون. قبل إضافة وحدة، تحقق مما إذا كانت هذه الوحدة جزءًا من الرسم البياني لـ Dagger بالفعل من خلال التحقق مما إذا كانت قد تمت إضافتها بالفعل إلى المكون أو بتجميع ومعرفة ما إذا كان بإمكان Dagger العثور على التبعيات المطلوبة لتلك الوحدة.
تنص الممارسات الجيدة على أنه يجب الإعلان عن الوحدات مرة واحدة فقط في أحد المكونات (بخلاف حالات استخدام Dagger المحدَّدة والمتقدّمة).
لنفترض أنه تمت تهيئة الرسم البياني بهذه الطريقة. ApplicationComponent
يشمل السعر Module1
وModule2
وModule1
وModuleX
.
Kotlin
@Component(modules = [Module1::class, Module2::class]) interface ApplicationComponent { ... } @Module(includes = [ModuleX::class]) class Module1 { ... } @Module class Module2 { ... }
Java
@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
يتم تضمينه مرتين في
الرسم البياني كما هو موضح في مقتطف الرمز التالي:
Kotlin
// 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 { ... }
Java
// 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.
Kotlin
@Component(modules = [Module1::class, Module2::class, ModuleX::class]) interface ApplicationComponent { ... } @Module class Module1 { ... } @Module class Module2 { ... }
Java
@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.
Kotlin
@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 { ... }
Java
@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 ويجب تمرير البعض الآخر في وقت الإنشاء من قبل المستخدم.
وفي نظام Android، يشيع هذا النمط في شاشات التفاصيل حيث يوجد معرّف العنصر المطلوب عرضه يكون معروفًا فقط في وقت التشغيل، وليس في وقت التجميع عندما يكون Dagger الرسم البياني DI. لمعرفة المزيد عن الحقن المدعوم باستخدام Dagger، اطّلِع على مستندات Dagger.
الخاتمة
راجِع قسم أفضل الممارسات إذا لم يسبق لك إجراء ذلك. إلى الاطّلاع على طريقة استخدام Dagger في تطبيق Android. راجِع مقالة استخدام أداة Dagger في تطبيق Android. درس تطبيقي حول الترميز.