أساسيات الخناجر

إدخال التبعية أو الخدمة يدويًا محددات مواقع الويب في تطبيق Android يمكن أن تكون مشكلة حسب حجم مشروعك. يمكنك الحد من تعقيد مشروعك أثناء زيادة حجمه باستخدام Dagger لإدارة التبعيات:

ينشئ Dagger تلقائيًا رمزًا يحاكي الرمز الذي كنت تستخدمه بخلاف ذلك. تكون مكتوبة بخط اليد. نظرًا لأنه يتم إنشاء التعليمات البرمجية في وقت التجميع، يمكن تتبعها وأكثر فعالية من الحلول الأخرى القائمة على الانعكاس، مثل Guice.

مزايا استخدام Dagger

يخلصك Dagger من كتابة رمز نموذجي مملّ ومعرض للأخطاء من خلال:

  • جارٍ إنشاء رمز AppContainer (الرسم البياني للتطبيق) الذي تريده يدويًا تنفيذها في قسم DI اليدوي.

  • إنشاء مصانع للفئات المتاحة في الرسم البياني للتطبيقات. هذا النمط هو كيفية إرضاء التبعيات داخليًا.

  • يُعد تحديد ما إذا كان سيتم إعادة استخدام تبعية أو إنشاء مثيل جديد من خلال استخدام النطاقات.

  • يمكنك إنشاء حاويات لتدفقات معيّنة كما فعلت مع تدفق تسجيل الدخول في القسم السابق باستخدام المكوّنات الفرعية لأداة Dagger. ويعمل ذلك على تحسين الأداء من خلال تحرير الكائنات في الذاكرة عندما لا تكون هناك حاجة إليها.

تفعل Dagger كل هذا تلقائيًا في وقت التصميم طالما أنك إعلان تبعيات إحدى الفئات وتحديد كيفية تلبيتها باستخدام التعليقات التوضيحية. ينشئ Dagger رمزًا برمجيًا مشابهًا لما كنت ستكتبه. يدويًا. داخليًا، ينشئ Dagger رسمًا بيانيًا للكائنات التي يمكنه الرجوع إليها لإيجاد طريقة لتوفير مثيل لأي فئة. لكل فئة في الرسم البياني، ينشئ Dagger فئة من نوع المصنع تستخدمها. داخليًا للحصول على مثيلات من هذا النوع.

في وقت التصميم، يرشدك 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:

  1. كيفية إنشاء مثيل UserRepository يتضمّن تعليقات @Inject التوضيحية الدالة الإنشائية.

  2. ما هي الاعتماديات الخاصة به: 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 رسمًا بيانيًا للتبعيات في مشروعك يمكنه استخدامها لمعرفة أين يجب أن تحصل على تلك التبعيات عندما تكون هناك حاجة إليها. لتنفيذ ذلك على Dagger، عليك إنشاء واجهة وإضافة تعليقات توضيحية إليها باستخدام @Component ينشئ Dagger حاوية كما تفعل عادةً باستخدام دليل حقن التبعية.

داخل الواجهة @Component، يمكنك تحديد دوال تعرض الافتراضية للفصول التي تحتاج إليها (أي UserRepository). يقول @Component: Dagger لإنشاء حاوية بجميع التبعيات المطلوبة لتلبية التي يعرضها. وهذا ما يُسمّى مكون 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)

في بعض الأحيان، تحتاج إلى مثيل فريد لتبعية في الحاوية. قد تحتاج إلى هذا لعدة أسباب:

  1. فأنت تريد أن تشارك الأنواع الأخرى التي لها هذا النوع كتبعية نفس مثل عدة كائنات ViewModel خلال مسار تسجيل الدخول باستخدام LoginUserData

  2. يعد إنشاء الكائن أمرًا مكلفًا ولا تريد إنشاء كائن جديد في كل مرة يتم تعريفها كتبعية (على سبيل المثال، محلل 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.