- PVSM.RU - https://www.pvsm.ru -
Привет! Хочу поделиться опытом создания ActivityScope. Те примеры, которые я видел на просторах интернета, на мой взгляд, не достаточно полны, неактуальны, искусственны и не учитывают некоторых нюансов практической разработки.
Статья предполагает, что читатель уже знаком с Dagger 2 и понимает что такое компонент, модуль, инжектирование и граф объектов и как все это вместе работает. Здесь же мы, в первую очередь, сконцентрируемся на создании ActivityScope и на том, как его увязать с фрагментами.
Итак, поехали… Что же такое scope?
Скоуп — это механизм Dagger 2, позволяющий сохранять некоторое множество объектов, которое имеет свой жизненный цикл. Иными словами скоуп — это граф объектов имеющий свое время жизни, которое зависит от разработчика.
По умолчанию Dagger 2 «из коробки» предоставляет нам поддержку javax.inject.Singleton скоупа. Как правило, объекты в этом скоупе существуют ровно столько, сколько существует инстанс нашего приложения.
Кроме того, мы не ограничены в возможности создания своих дополнительных скоупов. Хорошим примером кастомного скоупа может послужить UserScope, объекты которого существуют до тех пор, пока пользователь авторизован в приложении. Как только сессия пользователя заканчивается, или пользователь явно выходит из приложения, граф объектов уничтожается и пересоздается при следующей авторизации. В таком скоупе удобно хранить объекты, связанные с конкретным пользователем и не имеющие смысла для других юзеров. Например, какой-нибудь AccountManager, позволяющий просматривать списки счетов конкретного пользователя.
На рисунке показан пример жизненного цикла Singleton и UserScope в приложении.
Надеюсь, со скоупами немного разобрались.
Перейдем теперь к нашему примеру — ActivityScope. В реальных Android приложениях ActivityScope может оказаться крайне полезным. Еще бы! Достаточно представить себе какой-нибудь сложный экран, состоящий из кучи классов: пяток различных фрагментов, куча адаптеров, хелперов и презентеров. Было бы идеально в таком случае “шарить” между ними модель и/или классы бизнес логики, которые должны быть общими.
Есть 3 варианта решения данной задачи:
Давайте посмотрим по шагам как с помощью Dagger 2 создать и использовать ActivityScope.
Итак, для создания кастомного скоупа необходимо:
Интерфейс нашего демо-приложения [1] будет состоять из двух экранов ActivityА и ActivityB и общего фрагмента, используемого обоими активностями SharedFragment.
В приложении будет 2 скоупа: Singleton и ActivityScope.
Условно все наши бины можно разделить на 3 группы:
Каждый бин при создании получает уникальный id. Это позволяет наглядно понять, работает ли скоуп как задумывалось, потому что каждый новый инстанс бина будет иметь id, отличный от предыдущего.
Таким образом, в приложении будет существовать 3 графа объектов (3 компонента)
Перейдем к реализации. Для начала подключаем Dagger 2 к нашему проекту. Для этого подключим android-apt плагин в корневом build.gradle…
buildscript {
//...
dependencies {
//...
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
и сам Dagger 2 в app/build.gradle
dependencies {
compile 'com.google.dagger:dagger:2.7'
apt 'com.google.dagger:dagger-compiler:2.7'
}
Далее объявляем модуль, который будет провайдить синглтоны
@Module
public class SingletonModule {
@Singleton
@Provides
SingletonBean provideSingletonBean() {
return new SingletonBean();
}
}
и компонент синглтон:
@Singleton
@Component(modules = SingletonModule.class)
public interface SingletonComponent {
}
Создаем инжектор — единственный синглтон в нашем приложении, которым будем управлять мы, а не Dagger 2, и который будет держать Singleton скоуп даггера и отвечать за инжекцию.
public final class Injector {
private static final Injector INSTANCE = new Injector();
private SingletonComponent singletonComponent;
private Injector() {
singletonComponent = DaggerSingletonComponent.builder()
.singletonModule(new SingletonModule())
.build();
}
public static SingletonComponent getSingletonComponent() {
return INSTANCE.singletonComponent;
}
}
Объявляем ActivityScope. Для того, чтобы объявить свой скоуп, необходимо создать аннотацию с именем скоупа и пометить ее аннотацией javax.inject.Scope.
@Scope
public @interface ActivityScope {
}
Группируем бины в модули: разделяемый и для активностей
@Module
public class ModuleA {
@ActivityScope
@Provides
BeanA provideBeanA() {
return new BeanA();
}
}
@Module
public class ModuleB {
@ActivityScope
@Provides
BeanB provideBeanB() {
return new BeanB();
}
}
@Module
public class SharedModule {
@ActivityScope
@Provides
SharedBean provideSharedBean() {
return new SharedBean();
}
}
Объявляем соответствующие компоненты активностей. Для того чтобы реализовать компонент, который будет включать в себя объекты другого компонента, есть 2 способа: subcomponents и component dependencies. В первом случае дочерние компоненты имеют доступ ко всем объектам родительского компонента автоматически. Во втором — в родительском компоненте необходимо явно указать список объектов, которые мы хотим экспортировать в дочерние. В рамках одного приложения, на мой взгляд, удобнее использовать первый вариант.
@ActivityScope
@Subcomponent(modules = {ModuleA.class, SharedModule.class})
public interface ComponentActivityA {
void inject(ActivityA activity);
void inject(SharedFragment fragment);
}
@ActivityScope
@Subcomponent(modules = {ModuleB.class, SharedModule.class})
public interface ComponentActivityB {
void inject(ActivityB activity);
void inject(SharedFragment fragment);
}
В созданных сабкомпонентах объявляем точки инжекции. В нашем примере таких точек две: Activity и SharedFragment. Они будут иметь общие разделяемые бины SharedBean.
Инстансы сабкомпонентов получаются из родительского компонента путем добавления объектов из модуля сабкомпонента к существующему графу. В нашем примере родительским компонентом является SingletonComponent, добавим в него методы создания сабкомпонентов.
@Singleton
@Component(modules = SingletonModule.class)
public interface SingletonComponent {
ComponentActivityA newComponent(ModuleA a, SharedModule shared);
ComponentActivityB newComponent(ModuleB b, SharedModule shared);
}
Вот и всё. Вся инфраструктура готова, осталось инстанцировать объявленные компоненты и заинжектить зависимости. Начнем с фрагмента.
Фрагмент используется сразу внутри двух различных активностей, поэтому он не должен знать конкретных деталей об активности, внутри которой находится. Однако, нам необходим доступ к компоненту активити, чтобы через него получить доступ к графу объектов нашего скоупа. Чтобы решить эту «проблему», используем паттерн Inversion of Control, создав промежуточный интерфейс InjectorProvider, через который и будет строится взаимодействие с активностями.
public class SharedFragment extends Fragment {
@Inject
SharedBean shared;
@Inject
SingletonBean singleton;
//…
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof InjectorProvider) {
((InjectorProvider) context).inject(this);
} else {
throw new IllegalStateException("You should provide InjectorProvider");
}
}
public interface InjectorProvider {
void inject(SharedFragment fragment);
}
}
Осталось инстанцировать компоненты уровня ActivityScope внутри каждой из активностей и проинжектить активность и содержащийся внутри неё фрагмент
public class ActivityA extends AppCompatActivity implements SharedFragment.InjectorProvider {
@Inject
SharedBean shared;
@Inject
BeanA a;
@Inject
SingletonBean singleton;
ComponentActivityA component =
Injector.getSingletonComponent()
.newComponent(new ModuleA(), new SharedModule());
//...
@Override
public void inject(SharedFragment fragment) {
component.inject(this);
component.inject(fragment);
}
}
Озвучу еще раз основные моменты:
На первый взгляд может показаться, что для реализации такой простой задачи необходимо написать достаточно много связующего кода. В демо-приложении количество классов, выполняющих «работу» (бинов, фрагментов и активностей), примерно сопоставимо с количеством «связующих» классов даггера. Однако:
» Демо-проект доступен на гитхабе [1]
Всем Dagger и happy coding! :)
Автор: Тинькофф Банк
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android-development/203089
Ссылки в тексте:
[1] демо-приложения: https://github.com/d-tarasov/dagger-2-activity-scope
[2] Источник: https://habrahabr.ru/post/312196/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.