Android Architecture Components
Jan 27, 2018 · 6 min read
A new collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.
Why these components ?
A typical Android app is constructed out of multiple app components, including activities, fragments, services, content providers and broadcast receivers. Android app needs to be much more flexible as the user weaves their way through the different apps on their device, constantly switching flows and tasks. This app-hopping behavior is common, so your app must handle these flows correctly.
Keep in mi n d that mobile devices are resource constrained, so at any time, the operating system may need to kill some apps to make room for new ones. The point of all this is that your app components can be launched individually and out-of-order, and can be destroyed at anytime by the user or the system. Because app components are ephemeral and their lifecycle (when they are created and destroyed) are not under your control, you should not store any app data or state in your app components and your app components should not depend on each other.
The new architecture has following components that make it easy. They are also designed to fit together as building blocks.
The new architecture has following components that make it easy. They are also designed to fit together as building blocks.
A basic android app needs a database connect to a robust UI.
In new approach, lets tackle the database using Room which a new SQLite object mapping library. To setup a table in room you can define a Plain Old Java Object with annotation @Entity and @PrimaryKey.
For each POJO you need to define a Database Access Object (DAO). The annotated method represent the SQLite commands to interact with POJO data.
In above example Room automatically converts POJO object into corresponding database tables and back again.
Checks SQLite at compile time
Room also checks SQLite at compile time so if you spell something wrong or reference a column that is not into database it can show warnings at compile time like below
Error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such table: commentsZ)
LiveData
To enhance this approach we can use a new architecture component called LiveData. LiveData is a data holder class that keeps a value and allows this value to be observed. Unlike a regular observable, LiveData respects the lifecycle of app components, such that the Observer can specify a Lifecycle in which it should observe.
LiveData considers an Observer to be in an active state if the Observer’s Lifecycle is in STARTED or RESUMED state. If the Lifecycle is not in an active state (STARTED or RESUMED), the observer isn’t called even if the value changes. If the Lifecycle is destroyed, the observer is removed automatically. There might be multiple fragments and activities that are observing LiveData instance, and LiveData gracefully manages them such that it connects to the system service only if any of them is visible (that is, active).
LiveData can easily be used with Room to monitor the changes in database. Room will create a the live data object observing the database.
The LiveData class provides the following advantages:
- No memory leaks: Since the Observers are bound to their own Lifecycle objects, they are automatically cleaned when their Lifecycle is destroyed.
- No crashes due to stopped activities: If the Observer’s Lifecycle is inactive (like an activity in the back stack), they won’t receive change events.
- Always up to date data: If a Lifecycle starts again (like an activity going back to started state from the back stack) it receives the latest location data (if it didn’t already).
- Proper configuration change: If an activity or fragment is re-created due to a configuration change (like device rotation), it instantly receives the last available Location data.
- Sharing Resources: Now we can keep a single instance of class, connect to the system service just once, and properly support all observers in the app.
- No more manual lifecycle handling: Fragment can observe the data when it wants to, does not worry about being stopped or start observing after being stopped. LiveData automatically manages all of this since the fragment provided its Lifecycle while observing.
Lifecycle Owners and Lifecycle Observers
LifecycleOwners are objects with lifecycle like Activity and Fragments. LifecycleObservers observes LifecycleOwners and are notified of lifecycle changes.
Lifecycle uses two main enumerations to track the lifecycle status for its associated component.
The lifecycle events that are dispatched from the framework and the Lifecycle class. These events map to the callback events in activities and fragments.
The current state of the component tracked by the Lifecycle object.
Since the Architecture Components are in alpha stage, Fragment and AppCompatActivity classes cannot implement it (because we (Android) cannot add a dependency from a stable component to an unstable API). Until Lifecycle is stable, LifecycleActivity and LifecycleFragment classes are provided for convenience. After the Lifecycles project is released, support library fragments and activities will implement the LifecycleOwnerinterface. LifecycleActivity and LifecycleFragment will be deprecated at that time.
ViewModel
- The ViewModel class is designed to store and manage UI-related data so that the data survives configuration changes such as screen rotations.
- It is easier and more efficient to separate out view data ownership from UI controller logic. Lifecycles provides a new class called ViewModel, a helper class for the UI controller which is responsible for preparing the data for the UI. The ViewModel is automatically retained during configuration changes so that the data it holds is immediately available to the next activity or fragment instance.
- If the activity is re-created, it receives the same ViewModel instance that was created by the previous activity. When the owner activity is finished, the Framework calls ViewModel’s onCleared() method so that it can clean up resources.
To create a ViewModel class, you extend the ViewModel class and then put all the data that is necessary for you UI into this class.
Setup this view model in your activity or fragment like this:
The observe() method passes the LifecycleOwner as the first argument. Doing so denotes that this observer should be bound to that Lifecycle.
Источник
Android Architecture Components. Часть 1. Введение
На Google I/O 2017, было представлено набор библиотек под названием Android Architecture Components. В нескольких словах — это ряд вспомогательных библиотек, которые призваны помочь с такими вещами как, проектирование, тестирование и сопровождение приложений. То, что команда разработки Android начала акцентировать внимание на архитектуре не может не радовать, поскольку проблема является действительно актуальной. Ведь изначально не было предоставлено никаких требований или гайдлайнов по проектированию, и разработчику приходилось отталкиваться от своего предыдущего опыта. Что, в свою очередь, вызывало сложности в сопровождении проекта, а также сомнительные решения для специфических для ОС ситуаций. По факту это не первые шаги в этом направлении. Ранее уже Google представил репозиторий android-architecture с примерами применения разных архитектурных концептов. Надеемся, что развитие будет дальше и может на следующем Google I/O мы сможем увидеть полноценный фреймворк.
В целом Android Architecture Components можно разделить на четыре блока: Lifecycles, LiveData, ViewModel и Room Persistence.
Компонент Lifecycle – призван упростить работу с жизненным циклом. Выделены основные понятия такие как LifecycleOwner и LifecycleObserver.
LifecycleOwner – это интерфейс с одним методом getLifecycle(), который возвращает состояние жизненного цикла. Являет собой абстракцию владельца жизненного цикла (Activity, Fragment). Для упрощения добавлены классы LifecycleActivity и LifecycleFragment.
LifecycleObserver – интерфейс, обозначает слушателя жизненного цикла owner-а. Не имеет методов, завязан на OnLifecycleEvent, который в свою очередь разрешает отслеживать жизненный цикл.
Что это нам дает?
Назначение этого компонента – избавить разработчика от написания рутинного кода и сделать его более читаемым. Довольно частая ситуация, когда в нашем приложении работает ряд процессов, которые зависят от этапа жизненного цикла. Будь-то воспроизведение медиа, локация, связь с сервисом и т.д. Как итог нам приходится вручную отслеживать состояние и уведомлять о нём наш процесс. Что неудобно по двум причинам, захламление основного класса (Activity или Fragment) и снижение модульности, ведь нам нужно позаботиться про поддержку передачи состояния. С помощью же этого компонента мы можем переложить всю ответственность на наш компонент и все что для этого нужно это объявить интересующий наш класс как observer и передать ему в onCreate() методе ссылку на owner. В общем это выглядит так:
Наш обсервер абсолютно осведомлен о состоянии и может самостоятельно обрабатывать его изменение.
Компонент LiveData – являет собой holder класс для хранения объекта, а также разрешает подписаться к нему. LiveData знает про жизненный цикл и разрешает от него абстрагироваться.
Для имплементации компонента нам нужно расширить класс LiveData. Для работы нам нужно знать всего три метода из этого класса.
onActive() – этот метод вызывается когда у нашего экземпляра есть активный(-ые) обсервер. В нем мы должны инициировать интересующий нас сервис или операцию.
onInactive() – вызывается когда у LiveData нет активных слушателей. Соответственно нужно остановить наш сервис или операцию.
setValue() – вызываем если изменились данные и LiveData информирует об этом слушателей.
Если вы обратили внимание, то мы реализовали наш класс как Singleton. Это дает нам возможность использовать LiveData в других Activity, Fragment и т.д без переинициализации, если это нам не нужно.
Для того чтоб подписать слушателя, тоже никаких проблем нет. Все что нужно это вызвать метод observe у нашего экземпляра LiveData, передать в него LifeCycleOwner и реализацию интерфейса Observer. Интерфейс Observer имеет всего один метод onChanged(T t), с помощью него LiveData будет информировать слушателей об изменении в данных(вызов метода setValue(T t) в LiveData).
Что это нам дает?
Плюсов действительно много, начиная от защиты от memory leaks(Observer связан со своим Lifecycle и автоматически отписывается в случае, когда его Lifecycle уничтожен), защита от остановленной Activity(если, Lifecycle неактивен(stopped), то и нотификации на Observer не будут отправляться). С функциональных особенностей, это единый доступ к данным(с помощью singleton) и сбережение наших данных(Для таких операций как пересоздание активити, фрагмента). В целом же, назначение все то же, избавить разработчика от рутинной работы связанной с жизненным циклом.
Компонент ViewModel — спроектирован для хранения и управления данными которые связанные с представлением.
Задача же данного компонента, помочь разработчику абстрагировать данные и предоставить их хранение между такими операциями как пересоздание Activity. Если же нам необходимо сохранить небольшой набор данных, таких как item в RadioButtonGroup или же введенный данные, нам отлично подходит Bundle в onSaveInstanceState(). Но если это большой список к примеру: пользователей, товаров, каталог чего-нибудь нам приходилось заново доставать этот список. Вот в этом случае ViewModel является нашим основным помощником. Особенностью данного компонента является то что он привязывается к Activity и автоматически сохраняет свое состояние во время таких операций как onCofigurationChange().
Класс ViewModel, являет собой абстрактный класс, но не имеет абстрактных методов. Для реализации нашего класса нам нужно лишь наследоваться от ViewModel и описать данные которые мы хотим хранить и методы для их получения.
И это все, наш холдер для userList готов. Для того чтоб использовать наш холдер необходимо в методе onCreate(..) активити вызвать instance нашей модели:
С помощью ViewModelProviders, мы берем instance нашей модели. A c помощью if конструкции смотрим есть ли у нас уже данные в нашей модели или еще нет.
Что это нам дает?
Также как и предыдущие компоненты, этот помогает нам справится с особенностями и связанными с ними проблемами жизненного цикла Android. В этом случае, это отделение нашей модели представления данных от Activity и обеспечение безопасного механизма их хранения. Также при использовании совместно с LiveData не составляет проблем реализовать асинхронные запросы.
Компонент Room Persistence — Предлагает уровень абстракции над SQLite, предлагая более простой и продвинутый способ управления.
В целом же мы получили дефолтную ORM, этот компонент можно разделить на три части: Entity, DAO (Data Access Object), Database.
Entity — объектное представление таблицы. С помощью аннотаций можно легко и без лишнего кода описать наши поля.
Для создания нашей Entity нам нужно создать класс POJO (Plain Old Java Object). Пометить класс аннотацией Entity.
@PrimaryKey — Для обозначения ключа. @ColumnInfo — для связи поля в таблице. Создавать методы get и set не обязательно, но тогда нужно предоставить доступ к переменным с помощью модификатора public.
Установление связей объявляется также в теле аннотации Entity: Entity(foreignKeys = @ForeignKey(entity = Other.class, parentColumns = «id», childColumns = «author_id»))
DAO — Интерфейс который описывает методы доступа к БД.
Для реализации создаем интерфейс который помечаем аннотацией DAO и объявляем наши методы. Основные аннотации для методов это Insert Update Delete Query, в комментариях не нуждаются.
В обычном состоянии попытка получить доступ к БД с основного потока закончиться Exception. Если вы все же уверены в своих действиях, то можно воспользоваться методом allowMainThreadQueries(). Для асинхронного доступа рекомендовано использовать LiveData или RxJava.
Database — используется для создания Database Holder и является точкой доступа к соединению с БД.
Для создания нашего класса нужно наследоваться от RoomDatabase и использовать аннотацию Database, которой передаем параметры такие как используемые entity и версию базы. В теле описываем абстрактные методы доступа к нашим DAO.
Для создания instance для нашей БД используем код:
Для хранения db рекомендуют использовать singleton.
Что это нам дает?
Упрощение работы с базой данных, отпадает потребность в использовании сторонних ORM.
Кроме описанных выше плюшек NestedObject, параметры в запросах, коллекции аргументов, TypeConverter, database migration и т.д. Эти темы не входят в скоуп данного материала и будут рассмотрены позже.
К завершению материала хочу добавить еще несколько слов об архитектуре. С представлением Android Architecture Components была предложена архитектура решений, которая для большинства разработчиков уже и так привычна в той или иной форме. Суть заключается в том, что мы разбиваем архитектуру на 3 основных слоя: View (Activity/Fragment), который общается с ViewModel, а ViewModel работает непосредственно уже с Repository. Для наглядности преведу картинку с developer.android.com.
Выглядит абсолютно просто, а как на самом деле мы рассмотрим в следующих статьях.
Где сначала разберем более подробно каждый компонент и в конце реализуем приложение, как говорится с базой данных и… коннектом к сервису.
Источник