- MVVM на Android с компонентами архитектуры + библиотека Koin
- Введение
- Представление
- Модель представления
- Модель
- Как реализовать паттерн MVVM
- Сценарий приложения и реализация модели
- View-Model
- Представление
- Конкретизация объектов и внедрение зависимостей
- Заключение
- Почему я не использую SharedViewModel для фрагментов?
- Бонус: как я создаю фрагменты?
- Shared ViewModel in Android: Shared between Fragments
- Data sharing between Fragments
- Conclusion
MVVM на Android с компонентами архитектуры + библиотека Koin
Jan 13, 2020 · 7 min read
Введение
С MVVM ( Model— View-ViewModel) процесс разработки графического интерфейса для пользователей делится н а две части. Первая — это работа с языком разметки или кодом GUI. Вторая — разработка бизнес-логики или логики бэкенда (модель данных). Часть V iew model в MVVM — это конвертер значений. Это значит, что view model отвечает за конвертирование объектов данных из модели в такой вид, чтобы с объектами было легко работать. Если смотреть с этой стороны, то view model — это скорее модель, чем представление. Она контролирует большую часть логики отображения. Модель представления может реализовывать паттерн медиатор. Для этого организуется доступ к логике бэкенда вокруг набора юз-кейсов, поддерживаемых представлением.
В этом туториале мы попробуем определить каждый компонент паттерна MVVM, чтобы создать небольшое приложение на Android в соответствии с ним.
На следующей картинке — разные элементы, которые мы собираемся создать при помощи компонента Architecture и библиотеки Koin для внедрения зависимостей.
Архитектуру ниже можно разделить на три различные части.
Представление
Содержит структурное определение того, что пользователи получат на экранах. Вы можете поместить сюда статическое и динамическое содержимое (анимацию и смену состояний). Тут может не быть никакой логики приложения. Для нашего случая в представлении может быть активность или фрагмент.
Модель представления
Этот компонент связывает модель и представление. Отвечает за управление ссылками данных и возможных конверсий. Здесь появляется биндинг. В Android мы не беспокоимся об этом, потому что можно напрямую использовать класс AndroidViewModel или ViewModel.
Модель
Это уровень бизнес-данных и он не связан ни с каким особенным графическим представлением. В Android, согласно “чистой” архитектуре, модель может содержать базу данных, репозиторий и класс бизнес-логики. Картинка ниже описывает взаимодействие между разными компонентами.
Как реализовать паттерн MVVM
Чтобы реализовать паттерн MVVM, важно начать с компонентов, которым для работы нужен другой компонент. Это и есть зависимость.
А с момента появления компонента архитектуры, логичное общее решение — реализовать Android-приложения при помощи модели с изображения ниже. Там вы увидите стрелки, которые ведут от представления (активности/фрагмента) к модели.
А это значит, что View знает о View-Model, а не наоборот, и View Model знает о Model, и не наоборот. То есть у представления будет связь с моделью представления, а у модели представления будет связь с моделью. Строго в таком порядке, никак иначе. Благодаря такой архитектуре приложение легко поддерживать и тестировать.
Чтобы программировать быстро и эффективно, вам нужно начать с моделирования, так как модели не нужны другие компоненты для работы.
Сценарий приложения и реализация модели
Чтобы понять, как функционирует паттерн MVVM, мы напишем небольшое приложение, в котором будут все компоненты с предыдущей картинки. Мы создадим программу, которая покажет данные. Мы их взяли по этой ссылке. Приложение будет сохранять данные локально для того, чтобы потом оно работало в режиме оффлайн.
Приложение будет обрабатывать данные такой структуры. А для простоты я выберу всего лишь некоторые параметры. У класса GithubUser есть room-аннотация и у данных в локальной БД будет такая же структура, как и у данных в API.
У пространства DAO есть только два метода. Один — добавление информации в БД. Второй — ее извлечение.
Пространство базы данных выглядит так:
Во второй части мы реализуем Webservice, который отвечает за получение данных онлайн. Для того будем пользоваться retrofit+coroutines.
Если вы хотите узнать, как пользоваться Retrofit вместе с сопрограммами, загляните сюда .
В третьей части мы реализуем репозиторий. Этот класс будет отвечать за определение источника данных. Для нашего случая их два, так что репозиторий будет только получать данные онлайн, чтобы потом сохранить их в локальной базе данных.
Как сами видите, у репозитория есть конструктор с двумя параметрами. Первый — это класс, который представляет онлайн-данные, а второй — представляет данные оффлайн.
View-Model
После того, как мы описали модель и все ее части, пора ее реализовать. Для этого возьмем класс, родителем которого является класс ViewModel Android Jetpack.
Класс ViewModel создан для того, чтобы хранить и управлять данными, связанными с UI относительно жизненного цикла. Он позволяет данным пережить изменения конфигурации, например, повороты экрана.
View-model берет репозиторий в качестве параметра. Этот класс “знает” все источники данных для нашего приложения. В начальном блоке view-model мы обновляем данные БД. Это делается вызовом метода обновления репозитория. А еще у view-model есть свойство data. Оно получает данные локально напрямую. Это гарантия, что у пользователя всегда будет что-то в интерфейсе, даже если устройство не в сети.
Подсказка: я пользовался вспомогательным классом, который помогал мне управлять состоянием загрузки
Представление
Это последний компонент архитектуры. Он напрямую общается с представлением-моделью, получает данные и, например, передает их в recycler-view. В нашем случае представление — это простая активность.
В представлении происходит отслеживание того, как изменяются данные, как они автоматически обновляются на уровне интерфейса. Для нашего случая в представлении также отслеживается состояние операций загрузки в фоновом режиме. В процесс включено свойство loadingState, которое мы определили выше.
Вот вы и увидели, как я получил экземпляр view-model, используя для этого внедрение. А как это сработает, мы увидим дальше.
Конкретизация объектов и внедрение зависимостей
Наблюдательные заметят, что пока я еще не создал репозиторий и его параметры. Мы будет это делать точно при помощи внедрения зависимостей. А для этого в свою очередь мы берем библиотеку, Koin подходит идеально.
Так мы создадим важные объекты. Нашему приложению они нужны там же и нам останется только вызвать их в разные точки программы. Для этого и нужна магия библиотеки Koin.
В Module.kt есть объявление объекта, который нужен приложению. А в представлении мы берем inject, который говорит Koin, что нужен объект view-model. Библиотека в свою очередь старается найти этот объект в модуле, который мы определили ранее. Когда найдёт, назначит ему свойство userViewModel. А если не найдёт, то выдаст исключение. В нашем случае, код скомпилируется правильно, у нас есть экземпляр view-model в модуле с соответствующим параметром.
Похожий сценарий применится к репозиторию внутри view-model. Экземпляр будет получен из модуля Koin, потому что мы уже создали репозиторий с нужным параметром внутри модуля Koin.
Заключение
Самая сложная работа инженера ПО — это не разработка, а поддержка. Чем больше кода имеет под собой хорошую архитектуру, тем проще поддерживать и тестировать приложение. Вот почему важно пользоваться паттернами. С ними проще создать стабильно работающие программы, а не бомбу.
Вы можете найти полный код приложения у меня на GitHub по этой ссылке.
Источник
Почему я не использую SharedViewModel для фрагментов?
Задача организации взаимодействия между фрагментами встречается очень часто. На первый взгляд, ShareViewModel отлично подходит для этого. Мы создаем ViewModel с owner = наша activity, в которой отображаются наши фрагменты, и получаем эту ViewModel внутри каждого фрагмента. Т.к. владелец ViewModel — активити, то фрагменты получают один и тот же экземпляр ViewModel, что и позволяет им обмениваться данными, вызывать методы и т.д. Вот ссылка из документации.
На рисунке ниже представлена схема взаимодействия 3-х фрагментов.
Т.е. что мы делаем: в каждом фрагменте мы достаем SharedViewModel тех фрагментов, с которыми нам нужно взаимодействовать…
И это не самое лучшее решение, на мой взгляд. Потому что:
- Это полностью разрушает паттерн *VM. Мы строим ViewModel для конкретной View. ViewModel содержит разные LiveData, нужные этой View, необходимые методы и т.д. ViewModel не должна использоваться где-то ещё, кроме этой View.
- Каждая такая SharedViewModel — это лишняя зависимость для фрагмента. Нам нужно понимать, что в этой SharedViewModel происходит, что там нужно вызвать, чтобы второй фрагмент правильно отработал. И мы попросту можем что-то сломать.
- Нам нужно добавить во фрагмент все необходимые SharedViewModel, на которые этот фрагмент влияет или от которых зависит. И если в будущем появится еще фрагмент, с которым тоже надо взаимодействовать, то придется добавить в каждый фрагмент новую связь.
SharedViewModel можно использовать только тогда, когда есть несколько полностью одинаковых фрагментов с одинаковыми данными. В моей практике такого сценария еще не было. Может быть два одинаковых фрагмента, но состояние ViewModel каждого отличается, SharedViewModel для них недопустимо.
SharedViewModel приносит кучу проблем. Начинаешь путаться, все обрастает зависимостями, код усложняется. Гораздо проще работать, когда Fragment+ViewModel — это черный ящик.
Какое решение можно предложить?
Простой сервис, с которым каждый фрагмент взаимодействует по-своему.
Этот сервис должен передаваться внутрь каждой ViewModel. Благодаря DI и scope (Dagger, Koin, любые другие) это можно сделать легко и просто. Если сервис должен жить в контексте Activity, создайте его в Scope этой Activity и передайте каждой ViewModel.
Смотрите, что произошло: мы убрали все SharedViewModel из фрагментов. Каждый фрагмент теперь ничего не знает о других фрагментах. Если мы захотим добавить новый фрагмент в эту схему, то нам надо будет только связать viewModel D с нашим сервисом и все. Если фрагмент должен реагировать на изменение некоторого свойства сервиса — просто подписываемся на него. Нам больше не нужно разбираться в чужих ViewModels. И этот сервис будет гораздо проще любой ViewModel, разобраться в сервисе будет легче.
Может показаться, что на одном экране взаимодействующих фрагментов не так много. На телефоне — возможно. На планшете — уже нельзя так утверждать. А FragmentContainerView помогает использовать фрагменты в любых лейаутах, что очень удобно, если приложении много общих компонентов с жизненным циклом.
Бонус: как я создаю фрагменты?
Вот так: SomeFragment()
А что с параметрами?
С появлением ViewModel я не использую параметры. Я ничего не передаю во фрагмент через bundle (Android Studio до сих пор предлагает создавать фрагмент с аргументами и создавать фрагмент через static метод), я не использую Safety NavArgs для Navigation Component.
Все эти вещи делают фрагмент обусловленным. Мы не можем просто взять такой фрагмент и добавить в ViewPager, например. Надо думать, как передать параметры. Или другой пример: вы создали фрагмент с параметрами, работаете в нем, данные поменялись, фрагмент уже показывает что-то другое. Поменяли конфигурацию устройства (перевернули), фрагмент создался заново с устаревшими параметрами, в то время как ViewModel уже содержит другие данные. И начинается актуализация параметров и т.д.
Я это все убрал во ViewModel. Я использую такой же сервис, через который передаю параметры. Он работает наподобие Safety NavArgs. ViewModel запрашивает параметры у сервиса по типу класса параметров. После успешного извлечения параметра, сервис их удаляет (работает как Push/Pop).
Это сразу решает проблему смены конфигурации: фрагмент создался снова, а ViewModel уже с актуальными данными, все что нужно, это связать их со View.
Есть также проблема при передачи параметров через bundle — это превышение допустимого объема. Если объем передаваемых данных свыше 500 кб., то приложение может аварийно завершиться из-за возникшего исключения внутри Binder.java. Это привело к созданию разных библиотек, которые передают параметры через SD, что не очень быстро по сравнению с использованием RAM.
В общем, инструменты классные, все гибко, но нужно быть осторожным.
Источник
Shared ViewModel in Android: Shared between Fragments
Communication between Activities or Fragments in Android is a very common thing. Almost every application has some communication between various activities or fragments.
In this blog, we will learn how we can use the ViewModel in our application to communicate between various fragments in our application. We say it as SharedViewModel.
Data sharing between Fragments
Using SharedViewModel, we can communicate between fragments. If we consider two fragments, both the fragments can access the ViewModel through their activity.
Here, one fragment updates the data within the ViewModel which is shared between both the fragments and another fragment observes the changes on that data.
Let’s take a small example and learn it.
Note: Do not forget to add the dependencies required for ViewModel, LiveData.
First, we will create a class SharedViewModel.
Now, we are going to create two Fragments:
- MessageReceiverFragment : This Fragment is going to receive the message which will be sent by MessageSenderFragment. It will have a TextView which will show the received message.
- MessageSenderFragment : This Fragment is going to send the message which will be received by MessageReceiverFragment. It will have a Button to send the message.
Now, let’s update the activity_main.xml .
Our MainActivity Class.
Now, you are ready to run the app, see if it is working as expected. It should work.
Let’s discuss how it is working:
- Here, we have created an activity that consists of two fragments. The same activity is the host for both the fragment.
- In both the fragment, we have created the object of SharedViewModel which is the same object as we are using the same single activity as an owner. This is the reason it is shared. Notice that we have used the requireActivity() .
- In the MessageSenderFragment, we are sending the message on button click, which sets the value of message — LiveData in the SharedViewModel.
- Then, in the MessageReceiverFragment, we are observing the change in the message — LiveData , and whenever the message is coming, we are updating the data in textView.
This is how it works.
Important: Create SharedViewModel using the same owner.
Conclusion
In this blog, we learned about Shared ViewModel in Android to communicate with other fragments. In the last, we saw one example of fragment communication using Shared ViewModel. Hope you enjoyed this blog.
Источник