Основы кинжала,Основы кинжала

Внедрение зависимостей вручную или локаторы сервисов в приложении Android могут быть проблематичными в зависимости от размера вашего проекта. Вы можете ограничить сложность вашего проекта по мере его масштабирования, используя Dagger для управления зависимостями.

Dagger автоматически генерирует код, имитирующий код, который в противном случае вы написали бы вручную. Поскольку код генерируется во время компиляции, он отслеживается и более эффективен, чем другие решения на основе отражения, такие как Guice .

Преимущества использования Кинжала

Dagger освобождает вас от написания утомительного и подверженного ошибкам шаблонного кода благодаря:

  • Создание кода AppContainer (графа приложения), который вы вручную реализовали в разделе DI вручную.

  • Создание фабрик для классов, доступных в графе приложения. Вот как зависимости удовлетворяются внутри.

  • Решение о том, следует ли повторно использовать зависимость или создать новый экземпляр с помощью областей действия .

  • Создание контейнеров для определенных потоков, как вы это делали с потоком входа в систему в предыдущем разделе, с использованием подкомпонентов Dagger. Это повышает производительность вашего приложения за счет освобождения объектов в памяти, когда они больше не нужны.

Dagger автоматически делает все это во время сборки, если вы объявляете зависимости класса и указываете, как их удовлетворить, используя аннотации. Dagger генерирует код, аналогичный тому, который вы написали бы вручную. Внутри Dagger создает граф объектов, на которые он может ссылаться, чтобы найти способ предоставить экземпляр класса. Для каждого класса в графе Dagger генерирует класс фабричного типа , который он использует внутри себя для получения экземпляров этого типа.

Во время сборки Dagger просматривает ваш код и:

  • Строит и проверяет графики зависимостей, гарантируя, что:

    • Зависимости каждого объекта могут быть удовлетворены, поэтому исключений во время выполнения не возникает.
    • Циклов зависимостей не существует, поэтому нет бесконечных циклов.
  • Создает классы, которые используются во время выполнения для создания реальных объектов и их зависимостей.

Простой вариант использования в Dagger: создание фабрики

Чтобы продемонстрировать, как можно работать с Dagger, давайте создадим простую фабрику для класса UserRepository , показанную на следующей диаграмме:

Определите UserRepository следующим образом:

Котлин

class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Ява

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 :

Котлин

// @Inject lets Dagger know how to create instances of this object
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Ява

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 знает, как их создать:

Котлин

// @Inject lets Dagger know how to create instances of these objects
class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }

Ява

public class UserLocalDataSource {
    @Inject
    public UserLocalDataSource() { }
}

public class UserRemoteDataSource {
    @Inject
    public UserRemoteDataSource() { }
}

Компоненты кинжала

Dagger может создать график зависимостей в вашем проекте, который можно использовать, чтобы узнать, откуда следует получить эти зависимости, когда они потребуются. Чтобы Dagger мог это сделать, вам нужно создать интерфейс и аннотировать его @Component . Dagger создает контейнер так же, как если бы вы делали это при внедрении зависимостей вручную.

Внутри интерфейса @Component вы можете определить функции, которые возвращают экземпляры нужных вам классов (например, UserRepository ). @Component сообщает Dagger создать контейнер со всеми зависимостями, необходимыми для удовлетворения типов, которые он предоставляет. Это называется компонентом Dagger ; он содержит граф, состоящий из объектов, которые Dagger умеет предоставлять, и их соответствующих зависимостей.

Котлин

// @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
}

Ява

// @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 . Вы можете использовать его следующим образом:

Котлин

// 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()

Ява

// 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 каждый раз, когда его запрашивают.

Котлин

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository != userRepository2)

Ява

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, что хотите использовать тот же экземпляр. Это можно сделать с помощью аннотаций области видимости .

Обзор с помощью кинжала

Вы можете использовать аннотации области, чтобы ограничить время существования объекта временем жизни его компонента. Это означает, что один и тот же экземпляр зависимости используется каждый раз, когда необходимо указать этот тип.

Чтобы иметь уникальный экземпляр UserRepository при запросе репозитория в ApplicationGraph , используйте одну и ту же аннотацию области для интерфейса @Component и UserRepository . Вы можете использовать аннотацию @Singleton , которая уже включена в пакет javax.inject , который использует Dagger:

Котлин

// 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
) { ... }

Ява

// 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;
    }
}

Альтернативно вы можете создать и использовать пользовательскую аннотацию области. Вы можете создать аннотацию области следующим образом:

Котлин

// Creates MyCustomScope
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope

Ява

// Creates MyCustomScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}

Затем вы можете использовать его, как и раньше:

Котлин

@MyCustomScope
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@MyCustomScope
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val service: UserService
) { ... }

Ява

@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 .

Котлин

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository == userRepository2)

Ява

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository == userRepository2)

Заключение

Прежде чем использовать его в более сложных сценариях, важно ознакомиться с преимуществами Dagger и основами его работы.

На следующей странице вы узнаете, как добавить Dagger в приложение Android.