Android app data model

Архитектура Android-приложений… Правильный путь?

От переводчика: Некоторые термины, которые использует автор, не имеют общепринятого перевода (ну, или я его не знаю:), поэтому я решил оставить большинство на языке оригинала — они всё равно понятны и для тех, кто пишет под android, но не знает английский.
Куда писать об ошибках и неточностях, вы знаете.

За последние несколько месяцев, а также после дискуссий на Tuenti с коллегами вроде @pedro_g_s и @flipper83 (кстати говоря, 2 крутых Android-разработчика), я решил, что имеет смысл написать заметку о проектировании Android-приложений.

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

Приступая к работе

Мы знаем, что написание качественного программного обеспечения — сложное и многогранное занятие: программа должна не только соответствовать установленным требованиям, но и быть надёжной, удобной в сопровождении, тестируемой и достаточно гибкой для добавления или изменения функций. И здесь появляется понятие “стройной архитектуры”, которое неплохо бы держать в уме при разработке любого приложения.

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

  • Независимыми от фреймворков.
  • Тестируемыми.
  • Независимыми от UI.
  • Независимыми от БД.
  • Независимыми от любой внешней службы.

Кругов необязательно должно быть 4 (как на диаграмме), это просто схема; важно учитывать Правило Зависимостей: код должен иметь зависимости только во внутренние круги и не должен иметь никакого понятия, что происходит во внешних кругах.

Вот небольшой словарь терминов, необходимых для лучшего понимания данного подхода:

  • Entities (Сущности): это бизнес-логика приложения.
  • Use Cases (Методы использования): эти методы организуют поток данных в Entities и из них. Также их называют Interactors (Посредниками).
  • Interface Adapters (Интерфейс-Адаптеры): этот набор адаптеров преобразовывает данные из формата, удобного для Методов использования и Сущностей. К этим адаптерами принадлежат Presenter-ы и Controller-ы.
  • Frameworks and Drivers: место скопления деталей: UI, инструменты, фреймворки, БД и т. д.
    Для лучшего понимания обратитесь к данной статье или к данному видео.

Наш сценарий

Я начал с простого, чтобы понять, как обстоят дела: создал простенькое приложение, которое отображает список друзей, который получает с облака, а при клике по другу отображает более детальную информацию о нём на новом экране.
Я оставлю здесь видео, чтобы вы поняли, о чем идёт речь:

Android-архитектура

Наша цель — разделение задач таким образом, чтобы бизнес-логика ничего не знала о внешнем мире, так, чтобы можно было тестировать её без никаких зависимостей и внешних элементов.
Для достижения этого я предлагаю разбить проект на 3 слоя, каждый из которых имеет свою цель и может работать независимо от остальных.

Стоит отметить, что каждый слой использует свою модель данных, таким образом можно достигнуть необходимой независимости (вы можете увидеть в коде, для выполнения трансформации данных необходим преобразователь данных (data mapper). Это вынужденная плата за то, чтобы модели внутри приложения не пересекались). Вот схема, как это выглядит:

Читайте также:  Хорошие рингтоны для андроид

Примечание: я не использовал внешних библиотек (кроме gson для парсинга json и junit, mockito, robolectric и espresso для тестирования) для того, чтобы пример был более наглядным. В любом случае, не стесняйтесь использовать ORM для хранения информации или любой dependency injection framework, да и вообще инструменты или библиотеки, которые делают вашу жизнь легче (помните: изобретать велосипед заново — не лучшая идея).

Presentation Layer (Слой представления)

Здесь логика связывается с Views (Представлениями) и происходят анимации. Это не что иное, как Model View Presenter (то есть, MVP), но вы можете использовать любой другой паттерн вроде MVC или MVVM. Я не буду вдаваться в детали, но fragments и activities — это всего лишь views, там нету никакой логики кроме логики UI и рендеринга этого самого отображения. Presenter-ы на этом слое связываются с interactors (посредниками), что предполагает работу в новом потоке (не в UI-потоке), и передачи через коллбэки информации, которая будет отображена во view.

Если вы хотите увидеть крутой пример эффективного Android UI, который использует MVP и MVVM, взгляните на пример реализации от моего друга Pedro Gómez.

Domain Layer (Слой бизнес-логики)

Вся логика реализована в этом слое. Рассматривая проект, вы увидите здесь реализацию interactor-ов (Use Cases — методы использования).

Этот слой — модуль на чистой джаве без никаких Android-зависимостей. Все внешние компоненты используют интерфейсы для связи с бизнес-объектами.

Data Layer (Слой данных)

Все данные, необходимые для приложения, поставляются из этого слоя через реализацию UserRepository (интерфейс находится в domain layer — слое бизнес-логики), который использует Repository Pattern со стратегией, которая, через фабрику, выбирает различные источники данных, в зависимости от определенных условий.

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

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

Примечание: что касается кода, помня, что код — это учебный пример, я реализовал очень простой, даже примитивный кэш, используя хранения shared preferences. Помните: НЕ СТОИТ ИЗОБРЕТАТЬ ВЕЛОСИПЕД, если существуют библиотеки, хорошо решающие поставленную задачу.

Обработка ошибок

Это большая тема, в которой всегда есть, что обсудить (и здесь автор предлагает делиться своими решениями). Что до моей реализации, я использовал коллбэки, которые, на случай, если что-то случается, скажем, в хранилище данных, имеют 2 метода: onResponse() и onError(). Последний инкапсулирует исключения во wrapper class (класс-обертку) под названием “ErrorBundle”: Такой подход сопряжен с некоторыми трудностями, потому что бывают цепочки обратных вызовов один за другим, пока ошибка не выходит на presentation layer, чтобы отобразиться. Читаемость кода из-за этого может быть немного нарушена.

С другой стороны, я реализовал систему event bus, которая бросает события, если что-то не так, но такое решение похоже на использование GOTO, и, по-моему, иногда, если не управлять событиями предельно внимательно, можно заблудиться в цепи событий, особенно когда их одновременно бросается несколько.

Читайте также:  Неон тема для андроид

Тестирование

Что касается тестирования, я применил несколько решений, в зависимости от слоя.

  • Presentation Layer: существующий android инструментал и espresso для интеграции и функционального тестирования.
  • Domain Layer: JUnit + mockito использовались для юнит-тестов.
  • Data Layer: Robolectric (так как этот слой имеет android-зависимости) + junit + для интеграции и юнит-тестов.

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

Думаю, в этом месте вам интересно посмотреть на код. Что ж, вот ссылка на github, где можно найти, что же у меня получилось. Что стоит упомянуть о структуре папок так это то, что разные слои представлены разными модулями:

  • presentation: это android-модуль для presentation layer.
  • domain: java-модуль без android-зависимостей.
  • data: android-модуль, откуда поступают все данные.
  • data-test: тесты для data layer. Из-за определённых ограничений, при использовании Robolectric мне пришлось использовать отдельный java-модуль.

Заключение

Дядя Боб сказал: “Архитектура — это намерения, а не фреймворки”, и я полностью с ним согласен. Конечно, есть разные способы реализовать какую-либо вещь, и я уверен, что вы, как и я, каждый день сталкиваетесь с трудностями в этой области, но используя данные приёмы, вы сможете быть уверены, что ваше приложение будет:

  • Простым в поддержке.
  • Простым в тестировании.
  • Составлять единое целое,
  • Будучи разделённым.

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

Источник

Быстрый старт Data Binding в Android

Введение

Профессионально андроид-разработкой занимаюсь чуть больше года, до этого разрабатывал по Windows Phone и мне понравилась возможность связывать данные из вью модели с самим View при помощи механизма Bindings. А после изучения RX, многие задачи стали решаться более чисто, вью-модель полностью отделилась от View. Она стала оперировать только моделью, совсем не заботясь о том, как она будет отображаться.

В Android такой строгости я не заметил, Activity или Fragment как простейшие представители контроллера чаще всего имеют полный доступ как ко View, так и к модели, зачастуя решая, какой View будет видим, решая таким образом чисто вьюшные задачи. Поэтому я довольно радостно воспринял новость о появлении Data Binding в Android на прошедшем Google IO.

Пока что это только бета релиз, но уже можно протестировать функционал и увидеть направление, в котором двигаются разработчики из Google.

Начало

Я использую Android Studio 1.3. Data binding поддерживает Android 2.1 и выше (API level 7+).

Для сборки используется новый android плагин для Gradle (нужна версия 1.3.0-beta1 и старше). Так как связи отрабатываются во время компиляции, нам понадобиться ещё один плагин к Gradle ‘com.android.databinding:dataBinder:1.0-rc0’. В отличие от того же Windows Phone где механизм привязок реализован глубоко по средством DependencyProperty и в RealTime, в Android эта функция реализуется как бы поверх обычных свойств, во время компиляции и дополнительной кодогенерации, поэтому в случае ошибок будьте готовы разбирать ответ от компилятора.

Итак, заходим в файл build.gradle, который лежит в корневом каталоге проекта (в нём идут настройки Gradle для всего проекта). В блоке dependencies вставляем:

Теперь подключим плагин к конкретному модулю, откроем build.gradle файл, который лежит внутри модуля. По умолчанию app/build.gradle и добавим строчку:

Читайте также:  Cookie андроид как найти

Настройка Layout

Мы должны обернуть наш внешний View в тег
Уже сейчас можно начать его использовать класс Binding для доступа к элементам интерфейса, без использования findViewById. В MainActivity добавим поле и перепишем метод onCreate:

Название поля берётся из Id View, без Id в биндере поле не появиться, если изменить Id View, то поле в биндере сразу же переметнуться. Если с зажатым CTRL нажать на название поля View, то сразу перейдешь к нему в файле разметки. Как по мне так уже одного такого функционала достаточно для того чтобы начать использовать биндинги.

Привязка данных

Например у нас есть карточка пользователя имя и возраст.

Изменим Layout, заменим содержимое LinearLayout на:

И в onCreate заменим последнюю строку на:

Запускаем. Всё работает.
Наверное у всех проектах в активити или в фрагментах встречается такие строчки:

Тут то мы и начинаем использовать непосредственно привязки данных. Перепишем модель:

И добавим в Layout:

На красные выделения студии игнорируем.
Так как мы используем класс View, то его нужно импортировать, добавим в ноду
Или используем его вместе с названием пакета:

Так же возможно в ноде

Конвертеры

Импорт в свою очередь даёт возможность писать конвертеры. Добавим в модель поле с датой рождения и удалим возраст:

Импортируем его в разметку:

Обратная связь и Binding

Попробуем сменить имя пользователя.
Добавим в Layout:

Запускаем и кликаем, тост всплыл, но имя не изменилось. Это случилось из-за того, что модель ни как не известила binder о своём изменении.

Можно создать новую модель и вставить её, но с точки зрения памяти это расточительно:

Или вытащить старую, заменить данные и вставить опять:

Но тогда обновятся все View, связанные с этой моделью. Лучшим вариантом будет связать модель с binder, чтобы она могла его оповестить о своём изменении. Для этого перепишем класс модели, добавив геттеры и сеттеры,

помечая геттеры атрибутом @Bindable, и добавив в сеттеры вызов notifyPropertyChanged(BR.lastName);

Видим новый класс BR, в котором содержатся идентификаторы полей, чьи геттеры помечены атрибутом @Bindable . В Layout оставляем android:text=»@«, меняем только isAdult на adult, c ‘is’ в названии поля возникли проблемы. Запускаем всё работает.

ObservableFields

В пакете android.databinding есть классы, которые могут упростить нотификацию binder об изменении модели:

  • Обёртки над элементарными типами
  • ObservableField
  • ObservableArrayMap
  • ObservableArrayList

Попробуем изменить модель:

По коллекциям аналогично, единственное приведу пример обращения ко ключу к Map:

Из View в Model

Теперь попробуем взять новое имя из UI, привязав EditText к некоторой модели. Так как внутри EditText крутится Editable, то и привязать будет к
Изменю MainActivity:

А в разметку добавлю:

Вот тут возникает проблема, если привязать ObservableField к EditText, то всё будет работать только в сторону View. Как я понял, проблема в том, что Editable, который лежит внутри ObservableField, отличается от того, который лежит внутри EditText.

Если у кого есть идеи — делитесь.

Очень любопытно было увидеть библиотеку для поддержки Data Binding в Android от Google. В документации тоже нет информации про обратную связь данных, но я надеюсь на скорую её реализацию. После официального выхода стабильной версии можно будет посмотреть на интеграцию с JavaRX.

[ Оффициальная документация ]
[ Ссылка на мой простенький пример ]

Источник

Оцените статью