Dagger 2.11 & Android. Часть 2

в 10:16, , рубрики: android, dagger 2, dependency injection, java, mobile development, разработка мобильных приложений, Разработка под android

В предыдущей статье мы рассмотрели, как мы можем использовать специальный модуль dagger-android для предоставления зависимостей в активити и фрагменты, а также организацию разных скоупов.

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

Модуль dagger-android позволяет заинжектить зависимости в следующие базовые компоненты андроида:

Activity, Fragment, Service, DaggerIntentService, BroadcastReceiver, ContentProvider.

Если мы используем классы из библиотеки поддержки (например AppCompatActivity, android.support.v4.app.Fragment), то нам надо использовать соответствующие классы из дополнительной даггер библиотеки поддержки (dagger-android-support).

AndroidInjector

Служит для инъекций зависимостей в наследников базовых компонентов (Activity, Fragment, и т.д.).

@ContributesAndroidInjector

Данная аннотация должна быть применена над абстрактным методом в модуле, где возвращаемый тип метода — это наследник базового компонента андроид(Activity, Fragment и т.д.). Метод не должен иметь параметров.

Данная аннотация служит для генерации AndroidInjector для возвращаемого типа метода, над которым указана аннотация. Данный AndroidInjector является сабкомпонентом. Этот сабкомпонент является дочерним того компонента (или сабкомпонента), в который данный модуль(в котором присутствует данная аннотация) будет добавлен.

Аннотация содержит параметр modules. Данный параметр указывает на то, какие модули будут добавлены к данному сгенерированному сабкомпоненту.

Над методом с аннотацией @ContributesAndroidInjector также может присутствовать аннотация скоупа. Данный скоуп будет применен к сгенерированному сабкомпоненту.

AndroidInjectionModule / AndroidSupportInjectionModule

Встроенный модуль библиотеки dagger-android. Должен быть добавлен в root компонент.
Содержит в себе мультибайндиг коллекций с фабриками для создания сабкомпонентов, которые были сгенерированы с помощью аннотации @ContributesAndroidInjector. Для каждого базового компонента андроида своя коллекция.

DispatchingAndroidInjector

Является прокси AndroidInector, содержит коллекцию фабрик для создания сабкомпонента (AndroidInjector) для определенного типа. Например DispatchingAndroidInjector<Activity> содержит все фабрики создания сабкомпонентов для наследников Activity. При инжекте ищет нужную фабрику, создает сабкомпонент(AndroidInject) и инжектит зависимости.

AndroidInjection / AndroidSupportInjection

Класс утилита, имеет перегруженный метод inject для всех базовых типов (Activity, Fragment, Service и т.д.). В зависимости от переданного типа, ищет реализацию одного из следующих интерфейсов:

  • HasActivityInjector
  • HasFragmentInjector
  • HasServiceInjector
  • HasContentProviderInjector
  • HasBroadcastReceiverInjector

dagger-android-support

  • HasSupportFragmentInjector

Поиск реализации нужного интерфейса содержащий нужный AndroidInject происходит у объекта у которого время жизни выше.
Интерфейсы HasActivityInjector, HasServiceInjector, HasContentProviderInjector, HasBroadcastReceiverInjector должны быть реализованы в application. Интерфейсы HasFragmentInjector или HasSupportFragmentInjector могут быть реализованы в фрагменте или активити или в application, поиск реализации идет в следующем порядке: родительский фрагмент, активити в котором данный фрагмент находится и application.

AndroidInjection.inject() должен быть вызван в определенном методе, до вызова супер метода:

  • Activity — onCreate
  • Fragment — onAttach
  • Service — onCreate
  • IntentService — onCreate
  • ContentProvider — onCreate
  • BroadcastReceiver — onReceive

Dagger-android имеет классы, которые мы можем использовать (эти классы уже реализуют необходимые интерфейсы и вызов метода AndroidInjection.inject()):

  • DaggerApplication (HasActivityInjector, HasFragmentInjector, HasServiceInjector, HasBroadcastReceiverInjector, HasContentProviderInjector)
  • DaggerActivity (HasFragmentInjector)
  • DaggerFragment (HasFragmentInjector)
  • DaggerBroadcastReceiver
  • DaggerContentProvider
  • DaggerService
  • DaggerIntentService

dagger-android-support

  • DaggerApplication (такие же как и в DaggerApplication + HasSupportFragmentInjector)
  • DaggerAppCompatActivity (HasFragmentInjector, HasSupportFragmentInjector)
  • DaggerFragment (HasSupportFragmentInjector).

Если по каким то причинам мы не можем расширить один из этих классов, то мы всегда можем реализовать необходимый интерфейс.

Примеры использования dagger-классов:

Определим наш основной компонент:
Наш компонент должен наследовать AndroidInjector, т.к. при использовании DaggerApplication нам необходимо будет реализовать один метод, который должен возвращать AndroidInjector. Это только необходимо делать для application, т.к. мы вручную определяем главный компонент.

@Component(modules = {AppModule.class, AndroidSupportInjectionModule.class})
@Singleton
public interface AppComponent extends AndroidInjector<App> {

    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<App> {}
}

Определим основной модуль

В данном модуле мы добавим "маппинг" для нашей активити, сервиса и бродкаст ресивера. Для них будет сгенерированы сабкомпоненты и будут добавлены к основному компоненту, т.к. этот модуль мы подключили к основному компоненту.

@Module
abstract public class AppModule {

    @Provides
    @Singleton
    public static Context context(App app) {
        return app.getApplicationContext();
    }

    @Provides
    @Singleton
    public static UserRepository userRepository(Context context) {
        return new UserRepositoryImpl(context);
    }

    @ContributesAndroidInjector(modules = {
        ActivityModule.class
    })
    @ActivityScope
    abstract MainActivity mainActivity();

    @ContributesAndroidInjector(modules = {
        ServiceModule.class
    })
    @ServiceScope
    abstract MyIntentService myIntentService();

    @ContributesAndroidInjector
    @ReceiverScope
    abstract SomeReceiver connectionReceiver();
}

Определим наш класс Application

public class App extends DaggerApplication {

 @Override
 protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
     return DaggerAppComponent.builder().create(this);
 }
}

Пример с Activity

public class MainActivity extends DaggerAppCompatActivity {
    @Inject
    UserRepository userRepository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //userRepository уже можем использовать
    }
}

Пример с IntentService

public class MyIntentService extends DaggerIntentService {
    @Inject
    UserRepository userRepository;

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //userRepository уже можем использовать
    }
}

Пример с Receiver

public class SomeReceiver extends DaggerBroadcastReceiver {
    @Inject
    UserRepository userRepository;

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);

         //userRepository уже можем использовать
    }
}

Component lifecycle

При использовании dagger-android, компоненты и сабкомпоненты живут на протяжении жизни андроид компонентов(Activity, Fragment, Service и т.д.) в которых они были созданы и дестроить вручную их не надо. К примеру активити сабкомпоент создается в момент вызова AndroidInjection.inject() и живет до тех пор пока активити не уничтожена.

Dynamic parameters

C использованием dagger-android нам не нужно билдить сабкомпоненты и запрос зависимостей теперь достигается путем одного вызова AndroidInjection.inject(), а при использовании готовых классов из библиотеки dagger (например DaggerAppCompatActivity), мы можем сразу использовать зависимости. Может возникнуть такой вопрос, как мы можем сделать подобное:

long userId = getArguments().getLong(USER_ID);
getAppComponent()
            .plusUserComponent(new UserModule(userId))
            .inject(this);

userId является динамическим параметром для модуля. Данный параметр к примеру может использоваться для создания UserPresenter(UserRepository userRepository, Long userId).

Варианты реализации:

Вариант 1:
Установка параметров в объект, где он используется.

public class UserPresenter {
    private UserView userView;
    private UserRepository userRepository;
    private long userId;

    @Inject
    public UserPresenter(UserView userView, UserRepository userRepository) {
        this.userView = userView;
        this.userRepository = userRepository;
    }

    public void setUserId(long userId) {
        this.userId = userId;
    }
}

public class UserFragment extends BaseFragment implements UserView {
    @Inject
    UserPresenter userPresenter;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userId = getArguments().getLong(USER_ID);

        userPresenter.setUserId(userId);
    }
}

Данный вариант не подходит для immutable классов.

Вариант 2:

Перестроить сам класс таким образом, чтобы он не принимал динамические параметры при создании, а сам параметр использовать через методы:

public final class UserPresenter {
    private final UserView userView;
    private final UserRepository userRepository;

    @Inject
    public UserPresenter(final UserView userView, final UserRepository userRepository) {
        this.userView = userView;
        this.userRepository = userRepository;
    }

    public void loadUserInfo(long userId) {
        //
    }

}
public class UserFragment extends BaseFragment implements UserView {

    @Inject
    UserPresenter userPresenter;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userId = getArguments().getLong(USER_ID);

        userPresenter.loadUserInfo(userId);
    }
}

Вариант 3:

Реализовать создание объекта с динамическими параметрами с помощью фабрики:

public final class UserPresenter {
    private final UserView userView;
    private final UserRepository userRepository;
    private final long userId;

    public UserPresenter(final UserView userView, final UserRepository userRepository, long userId) {
        this.userView = userView;
        this.userRepository = userRepository;
        this.userId = userId;
    }

}

public final class UserPresenterFactory {
    private final UserView userView;
    private final UserRepository userRepository;

    @Inject
    public UserPresenterFactory(UserView userView, UserRepository userRepository) {
        this.userView = userView;
        this.userRepository = userRepository;
    }

    public UserPresenter create(long userId) {
        return new UserPresenter(userView, userRepository, userId);
    }
}

public class UserFragment extends BaseFragment implements UserView {

    @Inject
    UserPresenterFactory userPresenterFactory;

    UserPresenter userPresenter;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userId = getArguments().getLong(USER_ID);

        userPresenter = userPresenterFactory.create(userId);
    }
}

Написание подобных фабрик может оказаться не удобным или занимать некоторое время, для решение этой проблемы можно посмотреть в сторону AutoFactory.

Есть еще решение от Fernando Cejas, больше подходящее для тех, кто использует RxJava:
Dynamic Parameters in Use Cases

Вывод

Dagger-android позволяет предоставлять зависимости в базовые компоненты андроид (activity, fragment, service и т.д.) более удобным способом, избавляет нас от создания сабкомпонентов и контроля за ними. Активити, фрагменты, сервисы и т.д. выглядят более “чистыми”.

Надеюсь, данная статья помогла вам больше разобраться с возможностями dagger-android.

Автор: Евгений Надеин

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js