A small library a kind of architecture pattern implementation for android app's development and some samples
Идея подхода и, в какой-то, степени, архитектуры, состоит в том, чтобы попытаться решить основные типичные задачи, которые приходиться решать разработчикам, почти на каждом проекте, вне зависимости от того, какими библиотеками (чистое SDK или RxJava) или архитектурными подходами воспользоваться (MVP, MVVM, Clean Architecure). Данный подход своего рода гибрид разных подходов, но наверное скорее похож на MVVM, с некоторыми особенностями
Ви-Твикс (пусть будет называться так, в честь немаловажных событий), как уже отмечалось ранее, в какой-то степени гибрид
В описание архитектурного подхода вводяться следующие компоненты:
- TweexView – базовый объект, представляющий пользовательский интерфейс. В контектсте Android это будет активность или фрагмент. Аналог View в MVP. При создании, инициирует создание TweexViewModel и Coordinator
- TweexViewModel – объект, хранящий все данные, необходимые для отображения на пользовательском интерфесе. Должен быть максимально похож на POJO, если можно так выразиться, т.е. не выполнять никаких действий, только лишь инкапсулировать поля. Зачастую это просто поля TweexLiveData, либо дргуие POJOs. Чем-то похож на *ViewModel *в androidx (Android Arch), но в отличие от него, который переживает лишь пересоздание активности при повороте экрана, TweexViewModel в контексте *Coordinator *живет пока TweexView не будет уничтожена как более ненужная (onDestroy)
- Coordinator – компонент, связывающий воедино бизнес слой приложения (т.е. все модели, сервисы, базы и DAO) и пользовательский интерфейс. По факту, это адаптер или маппер, который преобразует данные, предоставляемые сервисами бизнес логики, в данные, хранящиеся в TweexViewModel.
- TweexLiveData и TweexMutableLiveData – интерфесы, которые нужны лишь для того, чтобы позволить отделиться от LiveData из androidx и иметь возможность тестировать все приложение, исключая UI часть с помощью обычных unit тестов без Robolectric и т.п.
- CoordinatorContainer – интерфейс для временного хранения работающего Coordinator
TweexView при создании создает TweexViewModel, и на основе ее создает соответсвующий Coordinator, который будет единственным на протяжении жизнинног цикла TweexView и для временного хранения (когда активность или фрагмент уничтожены после saveInstanceState) помещается в CoordinatorContainer, для последующего извлечения оттуда при onRestoreInstanceState
На диаграаме ниже представлено взаимодествие компонентов, для выполнения задачи некой загружки данных из сети Интернет. При этом пользователь может не дожидаться загрузки, а просто свернуть приложение на время загрузки (и система даже может уничтожить на это время активность или фрагмент), но загрузка данных продолжиться, и после воостановления окна приложения они будут корректно отображены. При этом нет обходимости перезагружать данные или выполнять какие-либо дополнительные действия, т.к. закэшированные в TweexViewModel данные через Observer отображены на экране.
Возможным минусом данного подхода можно назвать хранение в памяти информации, хотя в данный момент view не отображается. Однако это допущения было принято, в виду того, что зачастую данные необходимые для отображения малы по объему в сравнении с объемами памяти современных устройств. Для более сложных же задач (например картинки или другие данные) скорее всего будут применяться специфические подходы, т.к. данные все равно придется отображать на UI.
Следуюет сказать, что данный подход предполагает, что для хранения менающихся данных внутри TweexViewModel будут использоваться TweexLiveData или обычная *LiveData. *Такой подход позволит обновлять данные на UI и при этом, полями внутри *TweexViewModel *могут быть ссылки на Singleton LiveData или им подобные. Таким образом, можно реагировать на события и изменения происходящие в других частях приложения (например, список файлов для загрузки: файла загружаются в запущеном сервисе или в WorkManager, которые оновляют LiveData, ссылка на которую через Singleton объект является полем в TweexViewModel для данного TweexView)
Также отметим, что если пользователь уходит с активности через back, т.е. она более не нужна, то соответсующий Coordinator и связанная с ним TweexViewModel уничтожаются и не храняться больше в памяти
Плюсы
- Восстановление состояния и загруженных данных на onRestoreInstanseState
- Не заботимся о том (при правильном применении конечно), что потоки загрузки могут вызвать Listeners активности на обновление отдельных view, когда активность уже уничтожена
- Данные через LiveData/TweexLiveData отображаются на UI в актуальном состоянии
- Можем писать Unit тесты без использования Robolectrick сделав лишь Mock для TweexView
Недостатки
- Храним в памяти и выполняем в фоновом потоке логику, для активностей, которые в состоянии **stopped **(но не destoyed).
- Возможно не самым простым способом можем подключить DI (Dagger, Koin)
В GitHub есть реализация такого подхода, предоставля базовые классы для создания приложений с применением такого подхода (https://github.com/remdevlab/android-rem-wetweex)
Мы назвали библиотеку WeTweex. По размерам она достаточно легковесная, содержит базовые реалиации компонент и некоторые дополнения к ним.
Одним из немаловажных элементов библиотеки является ExecutableTask. Это небольшая обертка с использованием java.util.concurrency.Executor для постороения и выполения некой задачи в фоновом потоке. Результаты задачи можно получить через callback, которые также выполняются в фотовом потоке, а также задачу можно отменить.
ExecutableTask<String> generateStringTask = ExecutableTask.cancellable(
() -> textGenerator.generateText(1000L).getText()
).onCancel(() -> {
// do on cancel
}).onSuccess(srting -> {
// do some with generated string.
}).onError( throwable -> {
// do some with error
}).onComplete(() -> {
// called no matter failed or cancelled or succeeded
});
Ни один из onCancel, onSuccess, onError, onComplete не является обязательным. Они будут выполены в случае если они есть. Если их нет, то task выполниться но результат не будет известен.
Библиотека также содержит реализацию интерфейса Coordinator:
- SimpleCoordinator – реализует интерфейс Coordinator и также предоставляет методы для доступа к выполяемым задачам и ифнормируем о начале/окончании исполения задачи. Основным методом для работы является *submitTask, *который позволяет запустить задачу на исполение.
- ProgressWatchingCoordinator – наследник SimpleCoordinator, который работает уже с TweexProgressViewModel, что еще хранит в себе минимальную информаию о том, выполянется ли какая-либо задача. В паре с BaseTweexProgressObservingActivity позволяет автоматически отображать Progress Dialog, со списком исполняемых задач.
Типы задач
Задачи, выполняемые в приложении для отображения на UI, можно разделить на 3 группы:
- Отменяемые – результат не критичен, можем отменить. Например, зарузка файла, поиск.
- Неотменяемые – задачу нельзя отменять, т.к. это приведет к неправильной работе приложения (отправка нескольких запросов для подтверждения некой транзакции, т.е. не можем отменить в середине).
- Критические – пока задача не закончится, мы не можем ничего отобразить на экране. Отменяя ее, пользователь скорее всего желает уйти с этого экрана.
В соответсвии с этой классификацией, ExecutableTask предоставляет три статических метода для создания задачи:
- cancellable
- nonCancellable
- crucial
На основании этой классификации, реализация SimpleCoordinator определяет, что делать с текущей задачей, когда пользователь нажимает back и хочет покинуть активность или фрагмент (для этого в реализации TweexActivity есть логика позволяющая при попытке остановить задачу, узнать от coordiator можно ли закрыть экран или отменить задачу).
Для того, чтобы тестировать приложение, написанное подобным образом, можно использовать Mockito и обычные JUnit тесты. Применяя моки для TweexLiveData можно избежать необходимости использования Robolectric. (планируется выпутстить библиотеку wetweex-tests для поддержки тестов и генерации mock на основе TweexLiveData, покрывая ими поля типа TweexLiveData).
С использованием этой бибилотеки был создан небольшой пример, доступный на GitHub (https://github.com/remdevlab/android-rem-wetweex/tree/master/samples)