Pull to refresh

Comments 24

Огромное спасибо за диаграммы и стили изложения. Очень полезно для начинающих с DI. Продолжайте в том же духе!
Прекрасный цикл статей, очень не хватало когда начал использовать дагер! Огромное спасибо за труд.
Кстати, может быть стоит для удобства восприятия на диаграммах с компонентами где-то рядом с именем класса указывать имя модуля, в котором создаётся его экземпляр? То есть не AppComponent { Context, IDataRepository,… }, а что-нибудь вида
AppComponent { Context (AppModule), IDataRepository (DataModule),… }

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

И спасибо за статьи, просто отличные.
Если вам так будет удобнее понимать, то добавлю, конечно.
Насчёт инициализации screen-related компонентов из Application: меня смущает что получится, если экранов (активити, фрагменты) будет с несколько десятков: будет куча кода, к Application отношения не имеющего. Нарушается Single Responsibility Principle и прочая.

Возможно стоит вынести ответственность за создание таких компонент и за управление их временем жизни в соответствующие Presenters.
В Application я их инициализирую для удобства. Так то, конечно, инициализации разнесется по экранам. Все зависит еще от вашей обработки жизненного цикла.
Вы предлагаете инициализировать компоненты и контролировать их жизненный цикл в Презентерах?
Да, мне кажется, что там им самое место — ведь activity-related-компонент кроме как в контексте данной активити ведь и не нужен никому?

При этом разные DBManager-ы нужны именно Presenter-у, а не самой Activity — он из них что-то загрузил и потом результат в activity отправил.

С другой стороны, если используется простое приложение без MVP, с парой экранов — можно их, компоненты, там в Application и оставить.
Я и предположил, что в Application как раз и приведено создание, чтобы избыточную сложность не добавлять в пример.
Распространена реализация, когда прямо во фрагменте создается компонент и там хранится. Если нужна обработка ЖЦ, то фрагмент делается setRetainInstance(true). Как раз для screen-orientated.
Просто не представляю, как это через Presenter будет выглядеть. Плюс на Presenter ложиться новая ответственность — создание и контроль ЖЦ компонент.
А смотрите, в MVP ведь P как раз и отвечает за общение с внешним миром, выполнение запросов, сохранение результатов в БД, обработку ошибок — и проч., поэтому на мой взгляд логично ему и отдать компонент на хранение.
Практик обработки смены ориентации для активити, при которой presenter остаётся жив, несколько, лично мне вот этот подход нравится: http://engineering.remind.com/android-code-that-scales/

С точки зрения реализации особых проблем мне не видится, например так:
1. activity создалась, создался presenter со своими компонентами и зависимостями (или был взят уже ранее созданный в случае смены ориентации)
2. activity говорит: presenter.setView(this), ОК

теперь нужно в активити что-то заинжектить.
Вопрос: а зачем теперь в неё что-то инжектить? Ведь за связь с внешним миром отвечает сам presenter.

Нужно отметить, что правильный презентер сам выполнить inject для активити не сможет: ведь у него есть только интерфейс для активити, а для inject нужен конкретный тип.

Но если вдруг надо, то активити всегда может сделать так

presenter.setView(this);
presenter.getComponent.inject(this);

Как-то вот так.
Неплохая обработка сохранения/воспроизведения Presenters.
Но как-то по мне непривычная немного схема. Надо будет пообдумать.
Я поиграл, и пришёл к выводу, что цепочку инициализации всё-таки лучше с Activity начинать.

Если этим занимается presenter, ему нужно дать возможность создать ActivityScoped-компонент.
Для этого ему наверняка понадобятся другие (ApplicationScope) компоненты, уже созданные в Application.
Presenter про Application ничего не знает, значит Activity должна ему передать этот самый Application — либо необходимые компоненты.
Короче, получается некрасиво.

Ну а сам Presenter, как описано где-то в переписке, удобно между пересозданиями Activity в PresenterManager сохранять.
То есть при первом создании активити она создаёт presenter через dagger, а при пересозданиях этой активити — берёт его уже из presenterManager.
Либо же создание компонент можно вынести с activity и тому подобных. И все компоненты инициализировать через Application (или вспомогательный класс для Application).
Тогда нам вообще не нужно будет подстраиваться под ЖЦ, и все компоненты будут сосредоточены в специальном классе.
Например, в Application будет такое поле:
private ComponentStorage componentStorage;

А доступ к этому полю будет открыт из любого класса, и примерное обращение к конкретному компоненту будет такое:
MyApplication.getInstance().getComponentStorage().plusSomeComponent(...);

Но на нас ложиться полностью\контроль ЖЦ компонент. Хотя этот контроль на нас в любом случае.
Про Qualifier сказано совсем мало. Только что оно надо «если необходимы разные объекты одного типа».
Пример использования, мне кажется, не был бы лишним.
Тут https://github.com/googlesamples/android-architecture/tree/todo-mvp-dagger показано как можно использовать два «квалифайра» Local и Remote для работы с локальным хранилищем и удаленным.

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Local {
}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Remote {
}

@Singleton
public class TasksRepository implements TasksDataSource {

private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;

@Inject
TasksRepository(@Remote TasksDataSource tasksRemoteDataSource,
@Local TasksDataSource tasksLocalDataSource) {

mTasksRemoteDataSource = tasksRemoteDataSource;
mTasksLocalDataSource = tasksLocalDataSource;

}

@Override
public void getTasks(@NonNull final LoadTasksCallback callback) {
checkNotNull(callback);

if (mCacheIsDirty) {
// If the cache is dirty we need to fetch new data from the network.
getTasksFromRemoteDataSource(callback);
} else {
// Query the local storage if available. If not, query the network.
mTasksLocalDataSource.getTasks(callback);
}
}

//…
}
SCComponent является зависимым от ChatComponent, и он инъецирует зависимости в SingleChatFragment. При этом в SingleChatFragment данный компонент может инъецировать как SCPresenter, так и другие объекты родительских компонент, явно прописанные в соответствующих интерфейсах.


Провайдер из SsModule принимает Context Из AppComponent — а это не возможно, Context должен предоставить ChatComponent, так как зависимости в SsModule зависят от ChatComponent
Все верно, поправил это дело.
Спасибо за замечание!
Объясните пожалуйста, а как Dagger «чистит» за собой ресурсы, процесс создания графа зависимостей понятен, что делать когда Activity/Application будет уничтожено системой?
Тоже интересует этот вопрос. Который все обходят вниманием. К примеру, где-то «зануливают» subcomponent при OnDestroy(). А где-то нет. И не понятно нужно это делать или даггер/система сама позаботиться.
Тут все полностью на откуп разработчику.
Когда нет ссылок на компонент, он и его зависимости становятся доступны сборщику мусора, который все чистит.
Если есть хоть одна сильная ссылка, компонент будет жить.
То есть время жизни задает полностью разработчик.
Скоупы физически нужны для того, чтобы провайдить один объект для зависимости, а не каждый раз создавать новый. Кроме того мы придаем им еще некоторую смысловую нагрузку в виде «времени жизни». То есть названия скоупов как бы говорят сами за себя ("@Singleton", "@PerActivity" и т.д.), но физически временем жизни компонентов с этими скоупами управляем мы.
А можно узнать мнение/рекомендацию автора, как бы он поступил с описанным в статье примером в ситуации: Single Activity (активити живет дольше чем каждый из компонентов), SingleChatScreen и SettingsChatScreen — это фрагменты, которые занимают весь экран. Когда создавать тот или иной компонент и когда занулять его ссылки?
Спасибо
Прям вот скоро запилю статью про православие c Dagger 2 и многомодульностью.
Великолепно и очень наглядно! Спасибо.
Было бы супер по Hilt сделать подобный туториал.
Only those users with full accounts are able to leave comments. Log in, please.