«Dagger-Android & AAC» или «впихнуть невпихиваемое»

    image


    Сегодня хотелось бы поговорить о Dagger 2, в частности о dagger-android, Android Architecture Components, а так же о проблеме, с которой я столкнулся при их использовании. Наверное, пост не столько познавательный, сколько философский, сразу прошу не кидаться тапками, потому как причины создания поста есть (как минимум субъективные), о них расскажу под катом.


    Долго размышлял, стоит ли писать этот пост, но внутреннее равновесие пошатнулось, а значит посту быть.


    Дело вот в чем: наверняка многие из вас уже использовали вышеописанные инструменты в своих проектах, или хотя бы игрались с ними, дабы понять что это за зверь такой. Так и я наконец то пришел к тому, что надо бы использовать их в своем проекте.


    Теперь о том, чего я хотел добиться с помощью этих инструментов:


    • Забыть о кошмаре со сменой конфигурации при помощи «живучих» вьюмоделей
    • Использовать Dagger для поставки зависимостей прямо во вьюмодель
    • Организовать Scopes для зависимостей
    • Использовать новомодный dagger-android

    Теперь немного кода для лучшего понимания:


    Application
    class App : DaggerApplication() {
    
        override fun applicationInjector(): AndroidInjector<out App> = DaggerAppComponent.builder().create(this)
    
    }

    AppComponent
    @Singleton
    @Component(modules =
    [
        (AndroidSupportInjectionModule::class),
        (AppModule::class),
        (ActivityBuilder::class)
    ])
    interface AppComponent : AndroidInjector<App> {
        @Component.Builder
        abstract class Builder : AndroidInjector.Builder<App>()
    }

    ActivityBuilder
    @Module
    abstract class ActivityBuilder {
    
        @ActivityScope
        @ContributesAndroidInjector(modules = [MainActivityModule::class, FragmentProvider::class, RepositoryModule::class])
        abstract fun bindMainActivity(): MainActivity
    }

    RepositoryModule
    @Module
    class RepositoryModule {
    
        @Provides
        @ActivityScope
        fun provideSomeDataRepository(socket: SocketService, restApi: RestApi, feedDao: FeedDao, userSettings: UserSettings): SomeDataRepository
                = SomeDataRepositoryImpl(socket, restApi, feedDao, userSettings)
    

    Инъекция ViewModelFactory
        @Inject
        lateinit var factory: MainViewModelFactory
    
        private lateinit var viewModel: MainViewModel
    
        viewModel = ViewModelProviders.of(this, factory)
        .get(MainViewModel::class.java)

    ViewModelFactory
    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))
        }
    }

    ViewModel
    class MainViewModelImpl @Inject constructor(someDataRepository: SomeDataRepository) : MainViewModel(), MainViewModel.Input, MainViewModel.Output

    Так вот, насчет того списка, чего я хотел получить от всех этих манипуляций.


    Всё хорошо, всё работает, только вот я заметил одну особенность (чуть позже она стала очевидной): После смены конфигурации Dagger создает зависимости заново.Если поставить breakpoint на init блок в SomeDataRepositoryImpl, то мы увидим, что при добавлении фрагмента с такой же зависимостью — во фрагмент передается экземпляр того же объекта, что был создан при создании активности (breakpoint не сработал). Но при перевороте экрана, мы будем наблюдать создание нового объекта (breakpoint сработал), который не дойдет до вьюмодели, потому как она уже имеет экземпляр этого объекта.


    Из за небольшого опыта разработки с этими инструментами, было принято решение — спросить совета у других разработчиков, на что я получал ответы, что эта ситуация — норма, что сборщик мусора справится с этим бардаком, и всё будет нормально работать.


    Отсылаясь к заголовку поста, про "невпихиваемое", это было вычитано где-то в issues по dagger (не цитата слово в слово, но смысл такой же): "Вы пытаетесь вобрать всё лучшее из этих двух инструментов, не думаю, что это возможно"


    Спойлер

    Конечно, можно не использовать даггер-андроид, и подавать зависимости прямо во вьюмодель, используя попутно AndroidViewModel(application: Applitaction), но так не пойдет, ведь вьюмодель не должна знать о классе Application. Для меня до сих пор загадка — зачем этот класс вообще существует. Может кто-то объяснит в комментариях?


    Подводя итог ко всему выше сказанному, у меня один вопрос: Можно ли сделать то, что я хочу? Можно ли избавиться от этого неконтролируемого создания объектов даггера при смене конфигурации?


    Буду рад ответам в комментариях, спасибо за внимание.

    Поделиться публикацией
    Похожие публикации
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 17
    • 0
      MainViewModelFactory умирает при пересоздании активити, а так как она не является синглтоном, даггер создает новый обьект, подтягивает зависимость на репозиторий. Репозиторий помечен при помощи @ActivityScope, поэтому он не пересоздается, по идее если фабрике тоже дать скоуп, то и фабрика не будет пересоздаваться
      • 0
        Я это понимаю не так.
        Если пометить фабрику скоупом, то тогда она обязательно пересоздастся, потому как объекты в скоупе живут пока не умрет Activity
        • 0
          Правильно, но если ее пометить скоупом который лежит на уровень ниже, например если нам фабрика понадобиться на наскольких активити, то она не умрeт. Например ApplicationScope
      • 0
        -
        • 0
          Тогда получим конфликт, потому как нельзя внутри сабкомпонента аннотировать provide-методы более высоким скоупом
      • 0

        Я правильно понимаю, что при смене конфигурации пересоздаются Dagger-зависимости, но ViewModel остается прежней?

        • 0
          Правильно
          • 0

            Правильным решением было бы предотвращать пересоздание Activity-компонента при смене конфигурации (как — отдельный вопрос, есть несколько способов), но с dagger-android, насколько я понял, разработчик не контролирует время жизни компонента. Тогда остается либо вносить изменения в сам dagger-android, либо отказаться от него.

            • 0
              А можно подробнее про «несколько способов»?
              • 0
                1. Хранить компонент в retained headless fragment
                2. Сохранять компонент в saved state. Сериализация не нужна, если обернуть объект в Binder. Подробнее техника описана тут https://habrahabr.ru/post/274635/. Всё, что есть в статье не нужно, достаточно ValueBinder + BundleCompat.
                • 0
                  Спасибо за инфу, лайкнул бы, да кармы мало
        • 0
          Здравствуйте,
          Титульное изображение было скопировано с моего блога (https://www.techyourchance.com/dagger-2-scopes-demystified). Все права на его использование принадлежат мне, и я прошу вас немедленно его поменять и более не использовать.
          С уважением,
          В.Ц.

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

        Самое читаемое