قد يمثل تضمين التبعية اليدوي أو محددات مواقع الخدمة في تطبيق Android مشكلة بناءً على حجم مشروعك. يمكنك الحدّ من مدى تعقيد مشروعك لأنّه يتزايد باستخدام Dagger لإدارة التبعيات.
ينشئ Dagger تلقائيًا رمزًا برمجيًا يحاكي الرمز الذي كنت ستكتبه يدويًا. ونظرًا لأن الرمز يتم إنشاؤه في وقت التجميع، يمكن تعقّبه ويكون أكثر فعالية من الحلول الأخرى القائمة على الانعكاس مثل Guice.
مزايا استخدام Dagger
يوفر لك Dagger إمكانية كتابة تعليمات برمجية نموذجية مملة وعرضة للأخطاء عن طريق:
جارٍ إنشاء رمز
AppContainer
(الرسم البياني للتطبيق) الذي نفّذته يدويًا في قسم DI اليدوي.إنشاء مصانع للفصول المتوفرة في الرسم البياني للتطبيق. هذه هي كيفية رضا التبعيات داخليًا.
تحديد ما إذا كنت تريد إعادة استخدام التبعية أو إنشاء مثيل جديد من خلال استخدام النطاقات
إنشاء حاويات لتدفقات معينة كما فعلت مع تدفق تسجيل الدخول في القسم السابق باستخدام المكونات الفرعية لـ Dagger. ويؤدي ذلك إلى تحسين أداء تطبيقك عن طريق إطلاق عناصر في الذاكرة عند عدم الحاجة إليها.
ينفذ Dagger كل هذا تلقائيًا في وقت الإنشاء طالما أنك تعلن عن تبعيات لفئة معينة وتحدد كيفية تلبيتها باستخدام التعليقات التوضيحية. ينشئ Dagger تعليمات برمجية مشابهة لما كنت تكتبه يدويًا. داخليًا، ينشئ Dagger رسمًا بيانيًا للكائنات التي يمكن أن تشير إلى طريقة لتوفير مثيل من الفئة. لكل فئة في الرسم البياني، ينشئ Dagger فئة factory-type يستخدمها داخليًا للحصول على مثيلات من هذا النوع.
في وقت الإصدار، يتصفح Dagger التعليمات البرمجية الخاصة بك و:
تنشئ الرسوم البيانية للتبعية وتتحقّق من صحتها، لضمان ما يلي:
- يمكن استيفاء تبعيات كل كائن، لذلك لا توجد استثناءات في وقت التشغيل.
- لا توجد دورات تبعية، لذلك لا توجد حلقات لانهائية.
تنشئ الفئات المستخدمة في وقت التشغيل لإنشاء الكائنات الفعلية وتبعياتها.
حالة استخدام بسيطة في Dagger: إنشاء مصنع
لتوضيح كيف يمكنك العمل مع Dagger، لننشئ
مصنعًا بسيطًا لفئة UserRepository
كما هو موضح في
المخطط التالي:
حدِّد UserRepository
على النحو التالي:
Kotlin
class UserRepository( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } ... }
أضِف تعليق توضيحي من @Inject
إلى الدالة الإنشائية UserRepository
حتى يعرف Dagger كيفية إنشاء UserRepository
:
Kotlin
// @Inject lets Dagger know how to create instances of this object class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; // @Inject lets Dagger know how to create instances of this object @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
في مقتطف الرمز أعلاه، أنت تخبر Dagger:
كيفية إنشاء مثيل
UserRepository
باستخدام الدالة الإنشائية@Inject
ذات التعليقات التوضيحية.ما هي تبعياته:
UserLocalDataSource
وUserRemoteDataSource
.
يعرف Dagger الآن كيفية إنشاء مثيل UserRepository
، لكنه لا يعرف
كيفية إنشاء تبعياته. إذا قمت بإضافة تعليقات توضيحية إلى الفئات الأخرى أيضًا،
فإن Dagger يعرف كيفية إنشائها:
Kotlin
// @Inject lets Dagger know how to create instances of these objects class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... }
Java
public class UserLocalDataSource { @Inject public UserLocalDataSource() { } } public class UserRemoteDataSource { @Inject public UserRemoteDataSource() { } }
مكوّنات الخناجر
يمكن لـ Dagger إنشاء رسم بياني للتبعيات في مشروعك
الذي يمكنه استخدامه لمعرفة أين يجب أن يحصل على تلك التبعيات عندما تكون هناك حاجة إليها.
لتنفيذ هذا الإجراء في Dagger، يجب إنشاء واجهة وإضافة تعليقات توضيحية إليها باستخدام
@Component
. ينشئ Dagger حاوية كما كنت تفعل مع
حقن التبعية اليدوي.
داخل واجهة @Component
، يمكنك تحديد الدوال التي تعرض نسخًا افتراضية من الفئات التي تحتاج إليها (مثل UserRepository
). يطلب @Component
من Dagger إنشاء حاوية تتضمّن جميع التبعيات المطلوبة لاستيفاء الأنواع التي تعرضها. يسمى هذا مكون الخنجر؛ وهو يحتوي على رسم بياني يتكون من الكائنات التي يعرف Dagger كيفية تقديمها وتبعياتها الخاصة.
Kotlin
// @Component makes Dagger create a graph of dependencies @Component interface ApplicationGraph { // The return type of functions inside the component interface is // what can be provided from the container fun repository(): UserRepository }
Java
// @Component makes Dagger create a graph of dependencies @Component public interface ApplicationGraph { // The return type of functions inside the component interface is // what can be consumed from the graph UserRepository userRepository(); }
عند إنشاء المشروع، ينشئ Dagger عملية تنفيذ
واجهة ApplicationGraph
لك: DaggerApplicationGraph
. باستخدام معالج التعليقات التوضيحية، ينشئ Dagger رسمًا بيانيًا للتبعية يتألف من
العلاقات بين الفئات الثلاث (UserRepository
وUserLocalDatasource
وUserRemoteDataSource
) مع نقطة دخول واحدة فقط:
الحصول على مثيل UserRepository
. يمكنك استخدامه على النحو التالي:
Kotlin
// Create an instance of the application graph val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() // Grab an instance of UserRepository from the application graph val userRepository: UserRepository = applicationGraph.repository()
Java
// Create an instance of the application graph ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); // Grab an instance of UserRepository from the application graph UserRepository userRepository = applicationGraph.userRepository();
ينشئ Dagger مثيلاً جديدًا من UserRepository
في كل مرة يتم فيها طلبها.
Kotlin
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() val userRepository: UserRepository = applicationGraph.repository() val userRepository2: UserRepository = applicationGraph.repository() assert(userRepository != userRepository2)
Java
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); UserRepository userRepository = applicationGraph.userRepository(); UserRepository userRepository2 = applicationGraph.userRepository(); assert(userRepository != userRepository2)
أحيانًا تحتاج إلى وجود مثيل فريد للتبعية في الحاوية. قد ترغب في ذلك لعدة أسباب:
تريد أن تشارك الأنواع الأخرى من هذا النوع كتبعية المثيل نفسه، مثل كائنات
ViewModel
متعددة في مسار تسجيل الدخول باستخدام السمةLoginUserData
نفسها.إنشاء الكائن مكلف ولا تريد إنشاء مثيل جديد في كل مرة يتم تعريفه فيها كتبعية (على سبيل المثال، محلل JSON).
في المثال، قد ترغب في الحصول على مثيل فريد من UserRepository
في الرسم البياني بحيث تحصل دائمًا على الحالة نفسها في كل مرة تطلب فيها UserRepository
. ويُعدّ ذلك مفيدًا في المثال الذي تقدّمه لأنّه في تطبيق واقعي مع رسم بياني أكثر تعقيدًا، قد يكون لديك عدة كائنات ViewModel
بناءً على UserRepository
ولا تريد إنشاء مثيلات جديدة من UserLocalDataSource
وUserRemoteDataSource
في كل مرة يجب فيها تقديم UserRepository
.
من خلال حقن التبعية اليدوي، يتم تنفيذ ذلك من خلال تمرير مثيل UserRepository
نفسه إلى التركيبات البرمجية لفئات ViewModel، ولكن في "Dagger"، نظرًا لأنك لا تكتب هذا الرمز يدويًا، عليك إعلام
Dagger بأنّك تريد استخدام الحالة نفسها. يمكن القيام بذلك باستخدام التعليقات التوضيحية
على النطاق.
تحديد النطاق باستخدام Dagger
يمكنك استخدام التعليقات التوضيحية للنطاق لحصر عمر العنصر على فترة بقاء مكوّنه. هذا يعني أنه يتم استخدام نفس مثيل التبعية في كل مرة يلزم فيها تقديم هذا النوع.
للحصول على مثيل فريد من UserRepository
عند طلب المستودع في ApplicationGraph
، يمكنك استخدام التعليق التوضيحي على النطاق نفسه
للواجهة @Component
وUserRepository
. يمكنك استخدام التعليق التوضيحي @Singleton
الذي يتضمّن حزمة javax.inject
التي يستخدمها Dagger:
Kotlin
// Scope annotations on a @Component interface informs Dagger that classes annotated // with this annotation (i.e. @Singleton) are bound to the life of the graph and so // the same instance of that type is provided every time the type is requested. @Singleton @Component interface ApplicationGraph { fun repository(): UserRepository } // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph) @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
// Scope annotations on a @Component interface informs Dagger that classes annotated // with this annotation (i.e. @Singleton) are scoped to the graph and the same // instance of that type is provided every time the type is requested. @Singleton @Component public interface ApplicationGraph { UserRepository userRepository(); } // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph) @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; } }
بدلاً من ذلك، يمكنك إنشاء واستخدام تعليق توضيحي على نطاق مخصّص. يمكنك إنشاء تعليق توضيحي للنطاق على النحو التالي:
Kotlin
// Creates MyCustomScope @Scope @MustBeDocumented @Retention(value = AnnotationRetention.RUNTIME) annotation class MyCustomScope
Java
// Creates MyCustomScope @Scope @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomScope {}
وبعد ذلك، يمكنك استخدامه على النحو السابق:
Kotlin
@MyCustomScope @Component interface ApplicationGraph { fun repository(): UserRepository } @MyCustomScope class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val service: UserService ) { ... }
Java
@MyCustomScope @Component public interface ApplicationGraph { UserRepository userRepository(); } @MyCustomScope public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
وفي كلتا الحالتين، يتم توفير الكائن باستخدام النطاق نفسه المستخدَم لإضافة تعليقات توضيحية إلى واجهة @Component
. وبالتالي، في كل مرة يتم فيها الاتصال بـ
applicationGraph.repository()
، ستحصل على مثيل
UserRepository
نفسه.
Kotlin
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() val userRepository: UserRepository = applicationGraph.repository() val userRepository2: UserRepository = applicationGraph.repository() assert(userRepository == userRepository2)
Java
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); UserRepository userRepository = applicationGraph.userRepository(); UserRepository userRepository2 = applicationGraph.userRepository(); assert(userRepository == userRepository2)
الخاتمة
من المهم أن تكون على دراية بفوائد Dagger وأساسيات كيفية عملها قبل أن تتمكن من استخدامها في سيناريوهات أكثر تعقيدًا.
في الصفحة التالية، ستتعرّف على كيفية إضافة Dagger إلى تطبيق Android.