Разработка
Осваиваем ViewModel в Android: «можно» и «нельзя» — Часть 2
Избегая распространенных ошибок и используя правильные методы, мы можем сделать наши приложения сильнее, сохранить наши данные в безопасности и упростить тестирование.
Во второй части этой серии статей мы продолжим обсуждать лучшие практики использования ViewModel в Android.
В предыдущей части мы обсудили то, почему надо избегать инициализации состояния в блоке init {}.
Давайте посмотрим на ключевые моменты этой серии и освежим наши воспоминания. В этой части мы обсудим пункты 2 и 3 из списка.
Ключевые моменты для обсуждения в этой серии
- Избегайте инициализации состояния в блоке init{}.
- Избегайте раскрытия мутабельных состояний.
- Используйте update{} при использовании MutableStateFlows.
- Лениво внедряйте зависимости в конструктор.
- Примите более реактивное и менее императивное программирование.
- Избегайте инициализации ViewModel из внешнего мира.
- Избегайте передачи параметров из внешнего мира.
- Избегайте жесткого прописывания диспетчеров корутинов.
- Проводите модульное тестирование своих ViewModel.
- Избегайте раскрытия suspended функций.
- Используйте обратный вызов onCleared() во ViewModel.
- Обрабатывайте смерть процесса и изменения конфигурации.
- Вставляйте UseCases, которые вызывают Репозитории, которые, в свою очередь, вызывают DataSource.
- Включайте в ViewModel только доменные объекты.
- Используйте операторы shareIn() и stateIn(), чтобы избежать многократных обращений к восходящему потоку.
Избегайте раскрытия мутабельных состояний
Представление MutableStateFlow
непосредственно из Android ViewModel может вызвать ряд проблем, связанных с архитектурой приложения, целостностью данных и общей сопровождаемостью вашего кода. Вот некоторые из основных проблем.
Нарушение инкапсуляции
Основная проблема с раскрытием MutableStateFlow
заключается в том, что это нарушает принцип инкапсуляции в объектно-ориентированном программировании. Раскрывая мутабельный компонент, вы позволяете внешним классам напрямую изменять состояние, что может привести к непредсказуемому поведению приложения, трудноотслеживаемым ошибкам и нарушению ответственности ViewModel за управление и контроль собственного состояния.
Риски целостности данных
Когда внешние классы могут напрямую изменять состояние, поддержание целостности данных становится сложной задачей. ViewModel больше не может гарантировать, что ее переходы состояний действительны, что потенциально может привести к неправильным или противоречивым состояниям в приложении. Это может усложнить управление состоянием и повысить риск ошибок.
Повышенная сложность
Разрешение прямой модификации состояния вне ViewModel может привести к усложнению кодовой базы. Становится сложнее отслеживать, где и как инициируются изменения состояния, что делает кодовую базу менее понятной и сложной для сопровождения. Это также может усложнить отладку, так как становится менее понятно, как приложение достигло того или иного состояния.
Проблемы с параллелизмом
MutableStateFlow
безопасен для потоков, но управление параллелизмом становится сложнее, когда несколько частей приложения могут обновлять состояние одновременно. Без тщательной координации вы можете столкнуться с состоянием гонки или другими проблемами параллелизма, которые приведут к нестабильному поведению приложения.
Сложности тестирования
Тестирование ViewModel становится более сложным, когда ее внутреннее состояние может быть изменено извне. В тестах сложнее предсказать и контролировать состояние ViewModel, что может сделать тесты менее надежными и более сложными.
Архитектурная ясность
Представление изменяемых состояний вовне может размыть границы между различными слоями архитектуры вашего приложения. Роль ViewModel заключается в предоставлении данных и обработке логики для наблюдения и реагирования пользовательского интерфейса, а не в предоставлении мутабельного источника данных, который может быть изменен из любого места. Это может привести к менее четкому разделению задач, в результате чего архитектуру будет сложнее понять и сложнее следовать ей.
Отсутствие контроля над наблюдателями
Когда состояние может быть изменено извне, сложнее контролировать, как и когда наблюдатели получают уведомления об изменениях. Это может привести к ненужным обновлениям пользовательского интерфейса или пропуску обновлений, если состояние изменяется без надлежащего уведомления наблюдателей.
Ниже показан пример того, как можно выставить мутабельное состояние в качестве плохой практики.
class RatesViewModel constructor( private val ratesRepository: RatesRepository, ) : ViewModel() { val state = MutableStateFlow(RatesUiState(isLoading = true)) }
Как не показывать изменяемое состояние
Чтобы уменьшить эти проблемы, обычно рекомендуется раскрывать состояние ViewModel как доступное только для чтения, используя StateFlow
или LiveData
. Такой подход сохраняет инкапсуляцию и позволяет ViewModel более эффективно управлять своим состоянием. Изменения в состоянии можно вносить с помощью четко определенных методов во ViewModel, которые могут проверять и обрабатывать изменения по мере необходимости. Это позволяет обеспечить целостность данных, упростить тестирование и поддерживать четкую архитектуру.
class RatesViewModel constructor( private val ratesRepository: RatesRepository, ) : ViewModel() { private val _state = MutableStateFlow(RatesUiState(isLoading = true)) val state: StateFlow<RatesUiState> get() = _state.asStateFlow() }
В приведенном выше примере у нас есть внутреннее приватное состояние ViewModel, которое может быть обновлено внутри модели, а затем мы раскрываем неизменяемое состояние с помощью функции расширения asStateFlow()
.
Используйте update{} при использовании MutableStateFlows
Использование MutableStateFlow
в Kotlin, особенно в контексте разработки под Android, предлагает реактивный способ работы с данными, которые могут меняться с течением времени. Когда вам нужно обновить состояние, представленное MutableStateFlow
, есть несколько подходов, которые вы можете использовать. Давайте рассмотрим эти методы и почему использование .update{}
часто является рекомендуемым способом.
Вариант 1: Прямое присваивание
mutableStateFlow.value = mutableStateFlow.value.copy()
Этот метод предполагает непосредственную установку значения MutableStateFlow
путем создания копии текущего состояния с желаемыми изменениями. Этот подход прост и хорошо работает для простых обновлений состояния. Однако он не является атомарным, что означает, что если несколько потоков обновляют состояние одновременно, то могут возникнуть условия гонки.
Вариант 2: Эмиссия нового состояния
mutableStateFlow.emit(newState())
Использование .emit()
позволяет отправить новое состояние в MutableStateFlow
. Хотя .emit()
является потокобезопасной и может использоваться для одновременного обновления, это приостанавливающая функция. Это означает, что ее следует вызывать в рамках корутины, и она предназначена для ситуаций, когда вам может потребоваться подождать, пока состояние будет обработано. Это может быть более гибким, но также вносит сложности при использовании внутри синхронных блоков кода или вне корутин.
Вариант 3: Использование .update{}
mutableStateFlow.update { it.copy(// state modification here) }
Почему .update{}
часто является предпочтительным подходом:
- Атомарность:
.update{}
обеспечивает атомарность операции обновления, что очень важно в параллельной среде. Атомарность гарантирует, что каждое обновление применяется на основе текущего состояния, что позволяет избежать конфликтов между параллельными обновлениями. - Потокобезопасность: функция внутренне управляет безопасностью потоков, поэтому вам не придется беспокоиться о синхронизации обновлений состояния в разных потоках.
- Простота и безопасность: он обеспечивает простой и безопасный способ обновления состояния без необходимости явного управления корутинами, как это было бы в случае с
.emit()
для несинхронных обновлений.
В итоге, несмотря на то, что прямое присваивание и .emit()
имеют свои варианты использования, .update{}
разработан как потокобезопасный, атомарный способ обновления значений MutableStateFlow
. Это делает его отличным выбором для большинства сценариев, в которых вам нужно обеспечить последовательное и безопасное обновление реактивного состояния в параллельной среде.
Пример использования
Представьте, что у вас есть MutableStateFlow
, содержащий состояние типа User
, которое является классом данных:
data class User(val name: String, val age: Int) val userStateFlow = MutableStateFlow(User(name = "John", age = 30))
Если вы хотите обновить возраст пользователя, вы можете сделать это так:
userStateFlow.update { currentUser -> currentUser.copy(age = currentUser.age + 1) }
Этот код атомарно обновляет текущее состояние через userStateFlow
, увеличивая возраст на 1. CurrentUser
внутри лямбды представляет текущее состояние.
Убедитесь, что вы используете последнюю версию корутин, чтобы иметь возможность использовать эту функцию расширения:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0"
Резюме
Мы раскрываем передовые техники, критически важные для эффективной разработки приложений. Мы указали на подводные камни, связанные с прямым раскрытием изменяемого состояния из ViewModel, и обсудили связанные с этим риски. Для решения этих проблем мы рекомендовали такие решения, как использование состояния только для чтения и использование функции update{}
для более безопасного обновления состояния, что гарантирует, что наша кодовая база останется надежной и поддерживаемой.
Основные выводы
Из всего, что мы узнали, становится ясно, что придерживаться передовых практик в использовании ViewModel очень важно. Избегая распространенных ошибок и используя правильные методы, мы можем сделать наши приложения сильнее, сохранить наши данные в безопасности и упростить тестирование. Так что давайте следовать этим советам, чтобы создавать лучшие приложения для Android!
-
Новости1 месяц назад
Видеозвонки с Лили, Приключения и пианино — обновления Duolingo
-
Новости1 месяц назад
Видео и подкасты о мобильной разработке 2024.39
-
Видео и подкасты для разработчиков4 недели назад
Lua – идеальный встраиваемый язык
-
Новости4 недели назад
Poolside, занимающийся ИИ-программированием, привлек $500 млн