В предыдущей статье мы рассмотрели, как мы можем использовать специальный модуль 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.
Автор: Евгений Надеин