Использование фреймворка Hilt в полной мере — наша история успеха!

Хотите создать полный сквозной поток функций с надлежащим управлением состоянием, а также выдержать изменения конфигурации и, конечно же, избавиться от всех ошибок? Вот как Hilt помог нам создать наиболее подходящую архитектуру именно для этого варианта использования…

В чем проблема?

Несколько месяцев назад, когда мы изначально разрабатывали совершенно новую функцию, мы использовали одно действие + навигационный график вместе с одним ViewModel для управления всем потоком.

Мыслительный процесс, стоящий за этим, заключался в том, чтобы иметь центральное место для данных и обработки данных, но мы не смогли предвидеть растущую сложность сквозного потока!

В результате эта монолитнаяархитектура привела к тому, что данные и логическая обработка были сосредоточены в одном большом ViewModel с более чем 2 тысячами строк кода в одном файле!

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

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

Что мы сделали?

Мы проанализировали последние тенденции в разработке Android, несколько шаблонов проектирования в этом сценарии и придумали собственные моды для формирования новой подходящей архитектуры с помощью фреймворка внедрения зависимостей Hilt.

На следующей диаграмме показана общая архитектура, которой мы придерживались:

Используемые компоненты?

  1. ViewBinding
  2. Hilt для внедрения зависимостей
  3. NavGraph + Принцип одиночной активности
  4. ViewModels для сохранившихся конфигураций + логическая обработка
  5. Шаблон репозитория для операций ввода-вывода данных (комната + дооснащение)
  6. Компания Hilt предоставила экземпляр держателя данных с областью действия.
  7. Kotlin Coroutines для асинхронных задач
  8. Kotlin Flows для проверки формы ввода

Преимущества?

  1. Разбивает общую монолитную архитектуру на мелкие части, которыми легче управлять и обслуживать по отдельности.
  2. Наличие отдельного поставщика данных (Repository), держателя данных (источник данных) и обработчика данных (ViewModel) помогает создавать более чистый код, поскольку он эффективно следует парадигме разделения ответственности.
  3. Управление навигацией непосредственно из ViewModels, что упрощает управление состоянием навигации при сохранении высокой тестируемости.
  4. Управление индикаторами прогресса, всплывающими сообщениями, ресурсами и несколькими общими взаимодействиями из общей точки в ViewModels, что упрощает управление состоянием и тестирование.
  5. Полная совместимость с Kotlin Coroutines + API Flow, что приводит к чистому, лаконичному и полностью тестируемому асинхронному коду с меньшим количеством ошибок.
  6. Логическая обработка + управление состоянием происходит с ViewModels, поэтому пережить изменение конфигурации — детская игра!
  7. [Дополнительно] Использование привязки данных вместе с этой архитектурой еще больше уменьшит некоторые строки кода из-за фрагментов и активности.
  8. [Дополнительно] Полная поддержка Jetpack Compose, поскольку базовую архитектуру можно сохранить, просто изменив макеты на Composables.

Покажи мне код

Существенными компонентами всей архитектуры являются классы BaseViewModel, BaseFragment и DataSource, которые управляют почти всей архитектурой!

Код для BaseViewModel приведен ниже:

BaseViewModel здесь управляет почти всеми общими действиями, используемыми во всем потоке, включая навигацию от фрагмента к фрагменту (с аргументами навигации по умолчанию, поэтому XML-график навигации менее загроможден), отображение/скрытие диалогового окна прогресса (загрузчик), отображение соответствующих всплывающих уведомлений и т. д.

ViewModels может наследовать от этого BaseViewModel, чтобы использовать все функции напрямую!

Код для BaseFragment:

Базовый класс для всех фрагментов объекта, связанных навигационным графом. Он особенно поддерживает ViewBinding и наш любимый BaseViewModel, при этом наблюдая за общими данными в реальном времени, испускаемыми BaseViewModel.

Поскольку используется ViewBinding, создание и уничтожение представлений можно легко обобщить в этом фрагменте, поэтому дочерний фрагмент не должен заботиться об этом! (Его также можно использовать без ViewBinding, и в этом случае конструктор нуждается в небольшой настройке)

Обратите внимание, что этот базовый фрагмент содержит абстрактный экземпляр BaseViewModel, который каждый фрагмент может решать самостоятельно! (Так что, если некоторые из фрагментов все еще хотят поделиться ViewModel, это возможно)

Код для DataSource:

Сердце нашей функции, класс держателя данных или источник данных (вдохновленный чистой архитектурой) — это специальный предоставленный Hilt класс, который содержит собранные данные и переменные состояния на протяжении всего потока!

Особенностью этого класса является аннотация @ActivityRetainedScope! Это означает, что все члены, которым была внедрена эта зависимость, получат один и тот же ее экземпляр, пока они находятся в одном и том же экземпляре действия.

В противном случае это было возможно благодаря созданию класса Singleton, но Hilt дополнительно помогает сократить использование памяти, очищая нежелательные состояния после полного завершения потока!

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

Пример использования

Один из вариантов использования FragmentTwoздесь, наследующий от BaseFragment и выполняющий всю обработку пользовательского интерфейса в методе setupUI(), при этом заботясь о ViewModel действиях в отдельном выделенном методе setupVM().

Обратите внимание, что создание и удаление представления не обрабатывается отдельными фрагментами, а управляется самим BaseFragment! Гораздо меньше кода с правильно разделенными задачами.

Кроме того, поскольку мы позволяем фрагментам самостоятельно управлять созданием экземпляра ViewModel, мы можем напрямую использовать расширение by viewModels() для этой цели! Если какой-то фрагмент все еще хочет разделить ViewModel с другими фрагментами, это также возможно с использованием функций by navGraphViewModels() или by activityViewModels() в соответствии с требованием.

Ниже приведено ViewModelTwo, используемое в сочетании с нашим FragmentTwo:

ViewModelTwo помогает нашему FragmentTwo пережить изменения конфигурации, а также обработать и проверить поступающие от него события/данные!

Когда значение вводится в текст редактирования, оно передается в ViewModel для хранения и проверки, а поток открывается для фрагмента, который используется для дальнейшего управления состояниями, такими как включение кнопки «Продолжить» или видимость ошибки редактирования текста.

При нажатии кнопки введенное значение сохраняется в нашем хранилище данных, которое вводится с помощью Hilt, и это же значение затем может использоваться следующим ViewModel появляющимся потоком, поскольку экземпляр источника данных будет одинаковым для любой вводимый компонент.

Наконец, на основе введенного значения навигация выполняется из модели представления. Весь этот код полностью тестируется с точки зрения модульного тестирования, что приводит к большему охвату кода и меньшему количеству ошибок!

Вы можете ознакомиться с полной кодовой базой, целым работающим приложением, использующим эту архитектуру, на которой я построил: https://github.com/dkexception/architecture-with-hilt

Краткое содержание

Эффективное использование Hilt в сочетании с другими компонентами Android очень помогло нам вовремя разработать качественные функции.

Секрет разработки отличных приложений заключается в следовании некоторым основным принципам и стандартным рекомендациям. Эта архитектура решает многие распространенные проблемы, связанные с приложениями Android, и рекомендуется для разработки функций на основе потоков.

Что вы думаете об этой архитектуре? Хотели бы вы еще улучшить его? Вас что-то беспокоит? Пожалуйста, дайте мне знать в комментариях!

Особая благодарность Рахулу за то, что он предложил идею источника данных и всегда поддерживал ее на протяжении всей разработки потока!