Сегодня хотелось бы поговорить о Dagger 2, в частности о dagger-android, Android Architecture Components, а так же о проблеме, с которой я столкнулся при их использовании. Наверное, пост не столько познавательный, сколько философский, сразу прошу не кидаться тапками, потому как причины создания поста есть (как минимум субъективные), о них расскажу под катом.
Долго размышлял, стоит ли писать этот пост, но внутреннее равновесие пошатнулось, а значит посту быть.
Дело вот в чем: наверняка многие из вас уже использовали вышеописанные инструменты в своих проектах, или хотя бы игрались с ними, дабы понять что это за зверь такой. Так и я наконец то пришел к тому, что надо бы использовать их в своем проекте.
Теперь о том, чего я хотел добиться с помощью этих инструментов:
- Забыть о кошмаре со сменой конфигурации при помощи «живучих» вьюмоделей
- Использовать Dagger для поставки зависимостей прямо во вьюмодель
- Организовать Scopes для зависимостей
- Использовать новомодный dagger-android
Теперь немного кода для лучшего понимания:
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out App> = DaggerAppComponent.builder().create(this)
}
@Singleton
@Component(modules =
[
(AndroidSupportInjectionModule::class),
(AppModule::class),
(ActivityBuilder::class)
])
interface AppComponent : AndroidInjector<App> {
@Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
}
@Module
abstract class ActivityBuilder {
@ActivityScope
@ContributesAndroidInjector(modules = [MainActivityModule::class, FragmentProvider::class, RepositoryModule::class])
abstract fun bindMainActivity(): MainActivity
}
@Module
class RepositoryModule {
@Provides
@ActivityScope
fun provideSomeDataRepository(socket: SocketService, restApi: RestApi, feedDao: FeedDao, userSettings: UserSettings): SomeDataRepository
= SomeDataRepositoryImpl(socket, restApi, feedDao, userSettings)
@Inject
lateinit var factory: MainViewModelFactory
private lateinit var viewModel: MainViewModel
viewModel = ViewModelProviders.of(this, factory)
.get(MainViewModel::class.java)
class MainViewModelFactory @Inject constructor(private val someDataRepository: SomeDataRepository) : ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModelImpl(someDataRepository) as T
}
throw IllegalArgumentException(String.format("%s Not Found", modelClass.simpleName))
}
}
class MainViewModelImpl @Inject constructor(someDataRepository: SomeDataRepository) : MainViewModel(), MainViewModel.Input, MainViewModel.Output
Так вот, насчет того списка, чего я хотел получить от всех этих манипуляций.
Всё хорошо, всё работает, только вот я заметил одну особенность (чуть позже она стала очевидной): После смены конфигурации Dagger создает зависимости заново.
Из за небольшого опыта разработки с этими инструментами, было принято решение — спросить совета у других разработчиков, на что я получал ответы, что эта ситуация — норма, что сборщик мусора справится с этим бардаком, и всё будет нормально работать.
Отсылаясь к заголовку поста, про "невпихиваемое", это было вычитано где-то в issues по dagger (не цитата слово в слово, но смысл такой же): "Вы пытаетесь вобрать всё лучшее из этих двух инструментов, не думаю, что это возможно"
Конечно, можно не использовать даггер-андроид, и подавать зависимости прямо во вьюмодель, используя попутно AndroidViewModel(application: Applitaction), но так не пойдет, ведь вьюмодель не должна знать о классе Application. Для меня до сих пор загадка — зачем этот класс вообще существует. Может кто-то объяснит в комментариях?
Подводя итог ко всему выше сказанному, у меня один вопрос: Можно ли сделать то, что я хочу? Можно ли избавиться от этого неконтролируемого создания объектов даггера при смене конфигурации?
Буду рад ответам в комментариях, спасибо за внимание.
Комментарии (15)
nickchickin
27.02.2018 13:52-
Vertoletique Автор
27.02.2018 14:03Тогда получим конфликт, потому как нельзя внутри сабкомпонента аннотировать provide-методы более высоким скоупом
nickchickin
27.02.2018 14:19Следовательно, необходимо вынести Фабрику в AppModule и сделать ее синглтоном. И эта фабрика будет создавать все ViewModel для приложения. Я тоже столкнулся с такой проблемой, поскольку для каждого экрана делал свою фабрику, в нее включал зависимости, необходимые для ViewModel. Поэтому теперь применяю подход как в примере гугла github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/viewmodel/GithubViewModelFactory.java
Vertoletique Автор
27.02.2018 14:23Попробую этот вариант, спасибо за ссылку
Но почему-то мне кажется, что в таком случае фабрика не имеет доступ к модулям сабкомпонента, а значит все зависимости по этой логике будут @.SingletonLogos_Intellect
01.03.2018 13:35Тоже у себя использую пример Google, но на Kotlin.
gist.github.com/LogosIntellect/f786c218bea72cbfee69099d3d163ea2
deej
01.03.2018 13:35Я правильно понимаю, что при смене конфигурации пересоздаются Dagger-зависимости, но ViewModel остается прежней?
Vertoletique Автор
01.03.2018 13:35Правильно
deej
01.03.2018 15:00Правильным решением было бы предотвращать пересоздание Activity-компонента при смене конфигурации (как — отдельный вопрос, есть несколько способов), но с dagger-android, насколько я понял, разработчик не контролирует время жизни компонента. Тогда остается либо вносить изменения в сам dagger-android, либо отказаться от него.
Vertoletique Автор
01.03.2018 15:26А можно подробнее про «несколько способов»?
deej
01.03.2018 15:37- Хранить компонент в retained headless fragment
- Сохранять компонент в saved state. Сериализация не нужна, если обернуть объект в Binder. Подробнее техника описана тут https://habrahabr.ru/post/274635/. Всё, что есть в статье не нужно, достаточно
ValueBinder
+BundleCompat
.
nickchickin
MainViewModelFactory
умирает при пересоздании активити, а так как она не является синглтоном, даггер создает новый обьект, подтягивает зависимость на репозиторий. Репозиторий помечен при помощи@ActivityScope
, поэтому он не пересоздается, по идее если фабрике тоже дать скоуп, то и фабрика не будет пересоздаватьсяVertoletique Автор
Я это понимаю не так.
Если пометить фабрику скоупом, то тогда она обязательно пересоздастся, потому как объекты в скоупе живут пока не умрет Activity
nickchickin
Правильно, но если ее пометить скоупом который лежит на уровень ниже, например если нам фабрика понадобиться на наскольких активити, то она не умрeт. Например ApplicationScope