MVP и Dagger 2 – скелет Android-приложения – часть 2
Данная статья является результатом изысканий, побочным продуктом которых стало воплощение давней идеи в одном очень полезном и очень не хватавшем мне когда-то Android-приложении – My Location Notifier, предназначенном для автоматического оповещения адресата о прибытии пользователя (см. подробное описание по ссылке).
Итак, в первой части мы построили основной скелет Android-приложения в соответствии с архитектурой MVP. Теперь начнем прикручивать к нему Dagger 2.
2.1. Предварительная настройка
Для начала, перейдем в файл build.gradle (Project: mymvcapp) и добавим в раздел buildscript следующую строчку:
На момент написания этой статьи это последняя версия Dagger 2. Далее, перейдем в файл build.gradle (Module: app) и добавим в начало строчку:
Это нужно для того, чтобы в Dagger 2 мог генерировать свой код при компиляции. Позже поймете зачем.
Теперь добавим необходимые зависимости:
2.2. Подготовка к инъекции
Синхронизируем проект и вернемся к компоненту MainScreen. Создадим в пакете backstage класс di.MainScreenModule и пометим его аннотацией Module. В новом классе объявим и имплементируем метод providesPresenter(): MainScreenPresenter = MainScreenPresenter и пометим его аннотациями Provides и Singleton. Так теперь будет выглядеть наш класс:
Теперь перейдем в класс MainScreenCompatActivity и заменим модификаторы переменной presenter на lateinit var, удалим присвоение значения и пометим ее аннотацией Inject:
Заметьте, вопросительный знак нам теперь при обращении к этой переменной не нужен.
Может показаться, что все, инъекция произведена, задача выполнена. А вот и нет. Теперь нам необходимо заставить Dagger сгенерировать необходимый код для внедрения нашей первой зависимости. Именно для этого мы выше добавляли в скрипт сборки плагин kotlin-kapt. В пакете com.caesar84mx.mymvcapp создаем пакет di.config, в котором создадим интерфейс AppDiComponent.
Теперь, объявим интерфейс компонентом и синглтоном и мизераблем, зарегистрируем в нем наш модуль, а внутри интерфейса объявим метод inject(mainScreenActivity: MainScreen):
А теперь, необходимо, наконец, заставить Dagger сгенерировать весь необходимый код. Для этого, создадим в пакете config.di класс MyMvpApp, унаследуем его от класса Application, зарегистрируем класс в AndroidManifest.xml, прописав в теге application строчку android:name=».config.di.MyMvpApp». Далее, объявляем переменную lateinit var injector: AppDiComponent, устанавливаем ей приватный сеттер, переопределяем метод onCreate(). И начинаем магию:
Как вы успели заметить, класса DaggerAppDiComponent еще не существует, он будет сгенерирован при билде приложения. Равно как и имплементация нашего компонента. Имя класса составляется из слова “Dagger” + названия интерфейса, помеченного как компонент. Метод mainScreenModule() тоже будет сгенерирован при билде проекта, имя должно составляться из имени класса инъектируемого модуля в lowerCamelCase.
Собираем проект (Build → Make Project). Наслаждаемся автоматической кодогенерацией и продолжаем.
Внимание: далее, будут представлены некоторые пляски с бубном с элементами порно. Просьба убрать от экрана детей и нервных личностей.
Нам, для успешных инъекций будет необходима ссылка на переменную injector. Согласитесь, создавать экземпляр MyMvpApp в каждом классе, где мы производим инъекцию – не самое удачное решение. Поэтому, мы сделаем следующее:
Выдохнули, вернулись в класс MainScreen. Тепепь, в метод init() инжектим наш презентер. Не забудьте, что это действие необходимо выполнять до первого обращения к инъектируемой переменной. Так теперь выглядит наш класс:
А вот как выглядит вся базовая структура нашего приложения:
Итак, у нас готова минимальная структура приложения, на которую далее только остается навешивать элементы. Нужна новая активность? Представляем ее компонентом, отделяем ui от backstage, для каждого компонента определяем, какие зависимости нам нужны (минимум, презентер в активности, а может, в самом презентере API для взаимодействия с удаленным сервисом, или, например, API репозитория для работы с БД), прописываем модуль с зависимостями, регистрируем модуль в компоненте, прописываем в билдере, пересобираем проект, инжектим зависимости где необходимо, повторяем итерацию для каждого нового компонента.
Разумеется, может возникнуть вопрос: а зачем нам нужен Dagger? Без него ведь отлично справлялись? Отлично, пока приложение маленькое. Когда оно разрастется до полноценного, с десятками активностей, фрагментов, адаптеров, с запросами на сервер, кэшированием данных и прочими чудесами, возникнет множество зависимостей, отследить которые в большом приложении довольно трудно, если не использовать Dependency Injection. DI-фреймворк Dagger помогает упростить задачу их внедрения и отслеживания.
Источник
Вариант реализации MVP + DI(Dagger 2) в Android
С недавних пор заинтересовался данной темой и порылся в дебрях сети на эту тему. На англоязычных ресурсах есть раскиданная по разным местам информация. Прочитал все, что есть в рунете на эту тему. Ссылки приведу в конце статьи. В итоге, стал применять этот подход в своих приложениях.
Для начала, вспомним, что такое MVP. MVP — это паттерн, который позволяет «разбивать» приложение на три основных слоя (компонента):
- Модель (Model) — где сосредоточены данные;
- Представление (View) — интерфейс приложения (UI — элементы);
- Presenter — промежуточный компонент, который реализует связь между Моделью и Представлением.
MVP паттерн — это наследник более известного шаблона — MVC. Применение такого подхода позволяет отделить в приложении логику от интерфейса.
То, как сейчас реализована структура в приложении Android, с «натяжкой» можно назвать чем-то похожим на MVP. Т.е. модель — это какие-либо данные, представление — это UI-элементы в наших layout-xml файлах, а к presenter можно отнести Activity и Fragment. Но, это не так и не позволяет полностью отделить логику от данных и представления этих данных.
Теперь, давайте попробуем применить MVP в нашем Android приложении.
Для того, чтобы реализовать добавление ссылки на presenter во view применим еще один паттерн — Dependency Injection (DI). Для этого воспользуемся полезной библиотекой от Google – Dagger 2. В этой статье я не буду приводить описание библиотеки и ее компоненты, а также принципы ее работы. Подразумевается, что это нам уже известно.
Создадим тестовое приложение, которое будет выводить список конференций с сайта www.ted.com. Оно будет содержать одну главную activity и три фрагмента: фрагмент со списком, фрагмент с детализацией и фрагмент просмотра конференции.
Создание графа для DI вынесем в класс наследник Application:
В базовом абстрактном классе BaseActivity определим абстрактный метод setupComponent, который будет определять компонент для каждой активити в нашем приложении.
Для того, чтобы мы могли применить DI и для наших фрагментов, нам нужно:
1. Создать интерфейс IHasComponent, который будет имплементирован в каждой нашей активити:
2. Создать базовый абстрактный класс для фрагментов:
3. Создать интерфейс, кторый будет реализован в каждом Presenter для наших фрагментов:
Далее, создадим Module и Component классы для нашего Application:
Определим для наших активити компонентов отдельный скоуп как:
После этого мы можем написать класс активити для нашего приложения. В данном примере это одна главная активити. В коде приведены интересные нам методы, а полный код вы можете посмотреть на GitHub (ссылка на проект в конце статьи.)
Следующий шаг — это реализация наших фрагментов (приведу лишь код методов, которые нам интересны в плане реализации DI):
И последнее — это пример реализации наших классов presenter.
Эта статья не претендует на полное описание MVP и Dependency Injection, это еще один пример, как их можно применить в структуре Android приложения. Вначале может показаться, что нагромождение лишних классов и интерфейсов уменьшает читаемость кода, но это не так. После применения MVP на практике становится проще ориентироваться в приложении, код проще расширять.
Мы все больше программируем на уровне интерфейсов, а не реализации, компоненты приложения слабо связаны, что уменьшает количество ошибок при изменениях. А с использованием Dependency Injection код становится более лаконичным и читаемым.
Отмечу, что в данный момент в Dagger 2 есть один недостаток. Мы не можем делать в модуле override (это реализовано в Dagger от Square). Это создает проблемы при написании тестов. Надеюсь, что в последующих обновлениях библиотеки этот недочет будет исправлен. Кому интересна эта тема — есть пост на StackOverflow здесь и здесь.
Приведу ссылки на статьи, которые довели меня до такой жизни:
Полный код тестового проекта, который рассмотрен в статье, вы можете посмотреть и скачать на GitHub.
Источник
MVP и Dagger 2 – скелет Android-приложения – часть 1
Данная статья нацелена на новичков в Android-разработке и призвана помочь в создании минимально необходимой структуры приложения.
Так получилось, что я относительно недавно начал программировать под Android – после месяца без проекта в компании, где я работаю, меня определили в команду мобильной разработки в уругвайском офисе Tata Consultancy Services. При беседе с тимлидом команды мне был озвучен стек, с которым мне предстояло сначала ознакомиться, а затем и овладеть. В числе прочего был фреймворк Dagger 2 для DI и MVP в качестве архитектурного паттерна. И Kotlin. Но о нем в другой раз 🙂
Таким образом, я приступил к изучению сначала основы Android SDK, а затем и всего сопутствующего стека. С самим SDK проблем не возникло – исчерпывающей информации по нему в сети более чем достаточно, начиная с официальной документации и заканчивая туториалами (особенно с этим помог проект startandroid), но с Dagger 2 и MVP применительно к Android-разработке возникли некоторые затруднения ввиду довольно куцей документации первого и, на тот момент, недостаточного понимания второго. Дело в том, что до мобильной разработки я делал микросервисы на Java с использованием Spring Boot/MVC и уже имел достаточное представление и о том, что такое Dependency Injection, и о том, что такое MVC. При том, даже само название “Spring MVC” предполагает, что этот паттерн заложен в архитектуру проекта и его использование очевидно. От Dagger 2 я ожидал как такой же как в Spring “магии” и настолько же проработанной документации и туториалов. И обломался 😛
Тем не менее, при достаточном упорстве и усидчивости, нет ничего невозможного, а результатом изысканий стало осуществление моей давней идеи, возникшей еще в те времена, когда об Android-разработке я и не помышлял. Оценить идею вы можете, установив приложение из Google Store
В данной статье я хотел бы представить сухую выжимку результата моих поисков – пошаговое руководство по созданию скелета Android-приложения с использованием MVP и Dagger 2. Итак, начнем.
1.1 Abstracts
Для начала, создадим пакет abstracts в корне проекта, пусть это будет com.caesar84mx.mymvcapp.abstracts. В нем создадим 2 интерфейса: view.BaseView и presenter.BaseMvpPresenter. Следующим образом:
Это базовые архитектурные элементы, которые в дальнейшем будут использоваться в приложении. Далее, открываем BaseView и объявляем в него методы showView() getContext():
Теперь открываем BaseMvpPresenter и редактируем его следующим образом:
В пакете view создаем абстрактный класс BaseCompatActivity, наследуем его от AppCompatActivity и имплементируем недавно созданный интерфейс BaseView. Внутри класса объявляем абстрактный метод init(savedInstanceState: Bundle?) и имплементируем метод getContext() из BaseView:
От этого класса в дальнейшем мы будем наследовать все активности.
Теперь перейдем к презентеру – создадим класс BasePresenter, имплементирующий интерфейс BaseMvpPresenter и реализуем методы интерфейса следующим образом:
Отлично, базовые архитектурные элементы мы определили, теперь перейдем к компонентам, из которых будет строиться наше приложение.
1.2. Компоненты
Для начала, создадим пакет com.caesar84mx.mymvcapp.components, в нем пакет mainscreen, в котором, в свою очередь, пакеты ui и backstage, и перенесем в пакет ui класс MainScreen:
Теперь удалим из класса MainScreen автоматически сгенерированную при создании проекта имплементацию метода onCreate(), а также, наследование от AppCompatActivity и унаследуем его от BaseCompatActivity. Теперь реализуем метод init(), ранее объявленный в базовом классе. Весь код, который мы раньше поместили бы в метод onCreate(), мы поместим в него (как мы помним, метод init() вызывается в методе onCreate() базового класса):
Великолепно, элемент view паттерна MVP создан, теперь перейдем в закулисье нашего компонента – пакет backstage. Создадим интерфейс MainScreenContract – так называемый контракт, через который мы и будем реализовывать наш паттерн. В данном интерфейсе создадим 2 подинтерфейса — Presenter и View:
Теперь, перейдем к презетнеру и создадим класс MainScreenPresenter:
Скелет приложения почти готов, осталось несколько штрихов. В класс MainScreen добавим имплементацию интерфейса MainScreenContract.View, создадим и проинициализируем переменную presenter: MainScreenPresenter, а в методе init() присоединим вид к презентеру следующим образом:
Таким образом, мы создали презентер и добавили в него наш экземпляр view (не путать с android.view.View), который в презентере будет использоваться для манипуляций с видом.
1.3. Заключение первой части
Итак, мы создали базовые абстрактные элементы паттерна MVP, которые, однако, используются не напрямую, в лоб, а через т.н. контракт – базовый элемент каждого компонента приложения, который сочетает в себе как действия элемента view, так и действия элемента presenter. Контракт – достаточно гибкий элемент, состав которого варьирует от компонента к компоненту, ненавязчиво увязывая компоненты в рамках единой архитектуры.
Следует помнить, что в соответствии с концепцией MVP, элемент view должен быть максимально тупым, в нем мы производим только элементарные действия, такие, как, например, показать/спрятать текст, поменять фон или цвет текста, показать/спрятать значок загрузки и т.д. Методы, соответствующие этому элементу, мы определяем в подинтерфейсе View контракта. В то время, как логикой мы занимаемся в презентере – бизнес-логика, манипуляции данными (CRUD), запуск фоновых задач и т.д. В нем же мы решаем, когда и показать те или иные элементы на экране. В этом отличие от реализованной в спринге концепции MVC, где между бизнес-логикой и видом находится тупой контроллер, который только получает запросы от вида и вызывает сервис, который возвращает данные или выполняет иные действия, определенные бизнес-логикой. Методы, соответствующие презентеру, мы определяем в подинтерфейсе Presenter контракта.
При реализации презентера, манипуляции видом будут производиться через переменную view суперкласса BasePresenter, в то время, как методы, соответствующие view реализуются в классе активности.
Вы спросите, а где здесь Dagger 2 и зачем он нам сдался, не будет ли реализация DI в Android натягиванием совы на глобус? Ответ на второй вопрос – нет, не будет. А почему и зачем он нужен – во второй части моей статьи 😉
Источник