А вы знали, что почти все ViewModels нестабильны?
Когда мы только изучаем Compose,
нас учат использовать стабильный класс, а не нестабильный.
Но ViewModels нестабильны. Так почему же никто ничего не говорит о том, что мы используем нестабильные ViewModels?
🔹 Как Compose определяет стабильность?
Компилятор Compose считает класс стабильным, если:
val/var) неизменяемы (val).MutableState<T>).Взгляните на примеры:
// Стабильный класс
data class Stable(
val a: Int, // Стабильно
val b: MutableState<Int>, // Стабильно, отслеживается Compose
val c: SnapshotStateList<Int> // Стабильно, отслеживается Compose
)
// Нестабильный класс
data class Unstable(
var b: Int // Нестабильно из-за `var`
)
// "Неопределенная" стабильность
data class Runtime(
val i: Interface // Компилятор не знает, какая реализация будет на runtime.
)
Но есть важный нюанс: это правило работает только внутри модуля, где подключен компилятор Compose.
🔹 Что происходит на границах модулей?
Допустим, вы создали стабильную
data class в слое данных (data) и внедрили её в ViewModel в слое презентации.Логично ожидать, что
ViewModel тоже будет стабильным. Но на практике — нет!Compose-компилятор в модуле презентации не может заглянуть в модуль данных, чтобы проверить стабильность вашего класса. Поэтому он перестраховывается и помечает любой класс извне как нестабильный.
А раз наш
ViewModel зависит от репозиториев и UseCase из других модулей (domain/data), то и он сам автоматически становится нестабильным.🔹 Так почему же нестабильный ViewModel — это норма?
Ответ простой и лежит на поверхности: мы не передаем сам ViewModel в дочерние композаблы.
Вместо этого мы:
1. Создаем
ViewModel один раз наверху (например, в NavGraph).2. Коллектим его состояние (
state), которое уже является стабильным.3. Пробрасываем это стабильное состояние вниз по дереву композиции.
@Composable
fun Screen(viewModel: TestViewModel) { // ViewModel нестабилен, и это ок
val state by viewModel.state.collectAsState() // Состояние - стабильно
Child(state) // Передаем стабильный state
}
@Composable
fun Child(state: TestState) { // Стабильный пропс -> рекомпозиции оптимизированы
Text(state.data)
}
Compose-рантайм следит за изменениями в
state. Сам ViewModel как объект не «пробрасывается» глубже и не триггерит лишних рекомпозиций.🔹 Итоги
Так что можете спать спокойно — с вашим кодом всё в порядке.
#АрхитектурныйКод #MiddlePath #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1❤🔥1🔥1🤔1
adb shell am broadcast – отправка broadcast-интентов из командной строкиМощный инструмент для тестирования обработки broadcast-сообщений в вашем приложении без необходимости эмулировать системные события через UI.
# Отправка кастомного broadcast
adb shell am broadcast -a "com.yourapp.action.CUSTOM_ACTION"
# С дополнительными данными
adb shell am broadcast -a "com.yourapp.action.CUSTOM_ACTION" --es "key" "value"
1. Тестирование системных событий:
# Имитация подключения зарядки
adb shell am broadcast -a android.intent.action.ACTION_POWER_CONNECTED
# Имитация изменения языка системы
adb shell am broadcast -a android.intent.action.LOCALE_CHANGED
2. Тестирование кастомных broadcast receivers:
# Отправка с дополнительными extras
adb shell am broadcast -a "com.yourapp.action.DATA_REFRESH" \
--es "refresh_type" "full" \
--ei "user_id" 12345 \
--ez "force_update" true
3. Отправка конкретному пакету:
# Только для вашего приложения
adb shell am broadcast -a "com.yourapp.action.TEST" -n com.yourapp.package/.ReceiverName
Уведомление о низком заряде:
adb shell am broadcast -a android.intent.action.BATTERY_LOW
Изменение конфигурации:
adb shell am broadcast -a android.intent.action.CONFIGURATION_CHANGED
События времени:
adb shell am broadcast -a android.intent.action.TIME_SET
Какие broadcast-ы вы чаще всего тестируете через командную строку?
#буст #MiddlePath #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
🤝1
При создании интерфейсов на основе данных в SwiftUI вам часто будет требоваться визуализировать числовое значение или управлять им. Для этой цели можно использовать три встроенных представления —
Gauge, ProgressView, и Slider.Хотя на первый взгляд они могут показаться похожими, они существенно различаются по назначению и способу взаимодействия.
🔹 Индикатор — отображение значения в диапазоне
Представленный в iOS 16 вид
Gauge предназначен для отображения значения, а не для того, чтобы пользователи могли его изменять.Он идеально подходит для случаев, когда вам нужен индикатор только для чтения, например для отображения уровня заряда батареи, температуры или загрузки процессора.
Gauge(value: currentTemp, in: 0...100) {
Text("Temperature")
} currentValueLabel: {
Text("\(Int(currentTemp))°C")
}
.tint(.orange)
Gauge(value: currentTemp, in: 0...100) {
Text("Temperature")
} currentValueLabel: {
Text("\(Int(currentTemp))°C")
}
.tint(.orange)Индикатор может быть выполнен в нескольких стилях (
.linearCapacity, .accessoryCircular и других) и естественным образом адаптируется к различным макетам — от круглых индикаторов в стиле приборной панели до компактных виджетов.Используйте
Gauge , когда:🔹 ProgressView — индикатор выполнения задачи
ProgressView предназначен для отслеживания прогресса, а не для отображения числовых значений. Он показывает, какая часть задачи выполнена, либо детерминированно (известная доля), либо неопределённо (индикатор вращения).ProgressView(value: progress, total: 1.0)
.tint(.green)
ProgressView(value: progress, total: 1.0)
.tint(.green)
Он хорошо подходит для экранов загрузки/выгрузки, процессов адаптации или длительных операций.
Ключевое отличие от
Gauge заключается в контексте: значение представляет не реальное измерение, а состояние процесса.Используйте
ProgressView , когда:🔹 Ползунок — обеспечивает прямое управление пользователем
В отличие от
Gauge и ProgressView, Slider позволяет вводить данные. Это правильный выбор, если вы хотите, чтобы пользователь мог установить или изменить числовое значение, например яркость, громкость или интенсивность фильтра.Slider(value: $volume, in: 0...100) {
Text("Volume")
}
.tint(.blue)
Slider(value: $volume, in: 0...100) {
Text("Volume")
}
.tint(.blue)Slider напрямую связывается со свойством состояния с помощью Binding, что делает его оптимальным вариантом для любой интерактивной числовой настройки.Используйте
Slider , когда:🔹 Выбор правильного представления
При выборе из трёх вариантов:
Gauge.ProgressView.Slider.Каждый из этих видов соответствует принципам дизайна Apple, нап равленным на ясность и доступность. Понимание их сути поможет вам создавать интерфейсы, которые будут выглядеть правильно и вести себя естественно в экосистеме SwiftUI.
🔸 Курс «Основы IT для непрограммистов»
🔸 Получить консультацию менеджера
🔸 Сайт Академии 🔸 Сайт Proglib
#PixelPerfect #MiddlePath #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM