Какой текст выведется в лог если несколько раз нажать на кнопку?
Final Results
33%
recompose Foo, recompose Foo, recompose Foo
22%
recompose Foo text, recompose Foo text, recompose Foo text
44%
recompose Button's lambda, recompose Button's lambda, recompose Button's lambda
0%
Ни один из перечисленных - напишу свой в комментарий
И так, друзья.
Правильный ответ на наш квиз - вариант №3: recompose Button's lambda, recompose Button's lambda, recompose Button's lambda
Некоторые из вас ответили именно так!
Поздравляю!
Вся штука в переменной:
Это утверждение можно легко проверить, изменив наш код, и при клике подставив в text статичное значение:
Правильный ответ на наш квиз - вариант №3: recompose Button's lambda, recompose Button's lambda, recompose Button's lambda
Некоторые из вас ответили именно так!
Поздравляю!
Вся штука в переменной:
var text by remember { mutableStateOf("") }
При клике она меняется и таким образом меняется состояние элемента Text и скоуп кнопки, что заставляет Compose отрисовать ее с новым состоянием и кнопка становится все больше и больше.Это утверждение можно легко проверить, изменив наш код, и при клике подставив в text статичное значение:
Button(onClick = { text = "123" }) {
Log.d(TAG,"recompose Button's lambda $text")
Text(text)
}
Тогда при первом клике кнопка отобразит один раз наш текст 123 и далее стейт (состояние) при каждом клике уже меняться не будет.Еще немного про состояние (states) в Compose, но в контексте нашего демо-приложения:
https://github.com/Djangist/ComposeArchSample
Кто внимательно следит за постами, возможно помнит, что недавно наконец-то заработал SwipeRefresh, но если сделать свайп до конца - данные не обновляются.
Хотя в репозитории уже есть случайное генерирование температуры для моделек.
Чего же нам не хватает?
1. У нас есть вьюмодель, с данными, которые хранятся в StateFlow.
2. Мы подписались на эти данные и в случае их изменения, обновим UI
Вот наша главная composable-функция:
А не хватает нам всего лишь одного метода: collectAsState(),который уже добавлен как расширение для StateFlow.
Пара изменений в коде и данные по свайпу стали обновляться:
В описании к методу все наглядно (см скрин).
Каждое новое значение в StateFlow теперь будет приводить к recomposition, а это то, что нам и нужно!
Выглядит пока что это все неплохо. Поток данных можно довольно просто контролировать, обновляя лишь нужные кусочки UI.
Осталось добавить какой-то механизм, который будет управлять разными состояниями (например, Reducer в качестве части MVI-архитектуры/UI-паттерна) для полной красоты и усложнить наш пример, дабы посмотреть какие-то сложные кейсы и сложные состояния.
https://github.com/Djangist/ComposeArchSample
Кто внимательно следит за постами, возможно помнит, что недавно наконец-то заработал SwipeRefresh, но если сделать свайп до конца - данные не обновляются.
Хотя в репозитории уже есть случайное генерирование температуры для моделек.
Чего же нам не хватает?
1. У нас есть вьюмодель, с данными, которые хранятся в StateFlow.
2. Мы подписались на эти данные и в случае их изменения, обновим UI
Вот наша главная composable-функция:
@Composable
fun MainScreen() {
val viewModel: MainViewModel = hiltViewModel()
val mainWeather = viewModel.mainWeatherData.value
val daysWeather = viewModel.daysWeatherData.value
val hoursWeather = viewModel.hoursWeatherData.value
ShowWeather(
viewModel,
mainWeather,
daysWeather,
hoursWeather
)
}
А не хватает нам всего лишь одного метода: collectAsState(),который уже добавлен как расширение для StateFlow.
Пара изменений в коде и данные по свайпу стали обновляться:
val viewModel: MainViewModel = hiltViewModel()
val mainWeather = viewModel.mainWeatherData.collectAsState()
val daysWeather = viewModel.daysWeatherData.collectAsState()
val hoursWeather = viewModel.hoursWeatherData.collectAsState()
ShowWeather(
viewModel,
mainWeather.value,
daysWeather.value,
hoursWeather.value
)
В описании к методу все наглядно (см скрин).
Каждое новое значение в StateFlow теперь будет приводить к recomposition, а это то, что нам и нужно!
Выглядит пока что это все неплохо. Поток данных можно довольно просто контролировать, обновляя лишь нужные кусочки UI.
Осталось добавить какой-то механизм, который будет управлять разными состояниями (например, Reducer в качестве части MVI-архитектуры/UI-паттерна) для полной красоты и усложнить наш пример, дабы посмотреть какие-то сложные кейсы и сложные состояния.
Еще никогда не было так просто настроить CI/CD для Android-приложения.
После появления Github Actions (у Gitlab есть аналогичная штука) это можно сделать за три минуты!
Кайф!
Пост: https://proandroiddev.com/continuous-integration-delivery-for-android-with-github-actions-part-1-b232ed2b1740
После появления Github Actions (у Gitlab есть аналогичная штука) это можно сделать за три минуты!
Кайф!
Пост: https://proandroiddev.com/continuous-integration-delivery-for-android-with-github-actions-part-1-b232ed2b1740
Medium
“Continuous Integration/Delivery” for Android with GitHub Actions — Part 1
Dear developers, In this article I will show you how to automate the test execution and build of your Android app…
Забавный баг в Android Studio Arctic Fox (хотя, возможно, был и раньше).
Если остановить выполнение задачи (хорошо воспроизводится на задаче по сборке проекта), нажав на крестик (скрин 1), после остановки задачи, зеленая кнопка для запуска сборки проекта повторно не появляется (скрин 2) 🙂
Нужно запустить, например, Clean, чтобы кнопка появилась повторно.
Если остановить выполнение задачи (хорошо воспроизводится на задаче по сборке проекта), нажав на крестик (скрин 1), после остановки задачи, зеленая кнопка для запуска сборки проекта повторно не появляется (скрин 2) 🙂
Нужно запустить, например, Clean, чтобы кнопка появилась повторно.
Прекрасный доклад по модуляризации на посмотреть вечером, который покрывает и многие темы разработки ПО в более широком смысле!
https://www.youtube.com/watch?v=oAQlKiF91Ks
Кстати, на канале конференции еще множество интересных видосов.
https://www.youtube.com/watch?v=oAQlKiF91Ks
Кстати, на канале конференции еще множество интересных видосов.
YouTube
Степан Гончаров — Абсолютная модуляризация
Подробнее о конференции Mobius: https://jrg.su/ojGU3B
— —
. . .
Что, если вам больше не нужно быть Gradle экспертом, чтобы проектировать, поддерживать и эффективно масштабировать современные многомодульные Android-приложения, но при этом сразу же получить…
— —
. . .
Что, если вам больше не нужно быть Gradle экспертом, чтобы проектировать, поддерживать и эффективно масштабировать современные многомодульные Android-приложения, но при этом сразу же получить…
А вот и часть 3 хаотичного изучения Coroutines от RMR.
В новом выпуске ребята разобрали:
- Для чего был нужен SingleLiveEvent
- Как его приготовить без LiveData
- Channel
- О трате ресурсов в бекграунде
- buffer, conflate, flowOn, shareIn
- WhileSubscribed
- и многое другое.
Получился очень полезный выпуск.
В новом выпуске ребята разобрали:
- Для чего был нужен SingleLiveEvent
- Как его приготовить без LiveData
- Channel
- О трате ресурсов в бекграунде
- buffer, conflate, flowOn, shareIn
- WhileSubscribed
- и многое другое.
Получился очень полезный выпуск.
YouTube
Coroutines. Хаотичное изучение. Часть 3
Третья часть "хаотичного изучения" Kotlin Coroutines о том как безопасно слушать из UI. 🧔🏻
Зашли издалека:
- Для чего был нужен SingleLiveEvent 📟
- Как его приготовить без LiveData 🔫
- Channel (кажется это спойлер 😆)
- О трате ресурсов в бекграунде 🔦
- buffer…
Зашли издалека:
- Для чего был нужен SingleLiveEvent 📟
- Как его приготовить без LiveData 🔫
- Channel (кажется это спойлер 😆)
- О трате ресурсов в бекграунде 🔦
- buffer…
Пост 20. "Паттерн" репозиторий.
Многие уже знают о таком понятии как репозиторий. Однако, я все равно часто наблюдаю в коде проектов некое непонимание как правильно его "готовить".
Давайте разбираться.
В доках Гугла часто можно встретить примеры в которых во ViewModel напрямую ижектят репозитории, что мягко говоря, не очень правильно.
На самом деле, ViewModel или Presenter (смотря что используете) не должен ничего знать о репозиториях. Обычно с репой работает UseCase или Interactor. И именно он инжектится во ViewModel.
Но давайте о главном.
Что же такое репозиторий (Repository)?
Репозиторий можно рассматривать как коллекцию, которая работает с какой-либо моделью данных.
И только с одной моделью.
Это важно.
Если хочется работать с несколькими моделями, то самое правильное - это создать на каждую модель свой отдельный репозиторий.
Тут не стоит лениться - в дальнейшем будет меньше проблем.
Обычно репозиторий возвращает вам список моделей или конкретную модель, кеширует какие-либо локальные данные и возвращает эти данные из кеша и вот это вот все. Но в нем нет и не должно быть НИКАКОЙ другой логики (особенно бизнес-логики). Чем он тупее - тем лучше. Положили модель куда-то, отдали модель.
Теперь немного про слои.
Самые первые реализации Clean Arctitecture под Android делили слои репозиториев так:
1. Интерфейс репозитория мы помещаем на уровне domain.
2. Реализацию (класс, наследующий интерфейс репозитория) на уровне data.
3. UseCases / Interactors инжектит в конструктор именно интерфейс репозитория, а не реализацию! Это тоже очень важно. Реализацию подставит DI-фреймворк.
Сейчас же, когда проекты стали делить на features (фичи), а еще большие проекты на features в отдельных модулях, принцип разделения на слои остался прежним (в каждом модуле фичи своя структура domain - data - presentation).
Еще стоит отдельно упомянуть, что репозитории могут реализовывать разные DataSource (обычно инжектится в конструктор), например:
Для чего это нужно?
Ну например, один заинжекченный репозиторий работает с локальным кешем данных, а другой всегда получает данные из сети. Интерфейс репозитория один и тот же, но за счет подстановки нужной реализации DataSource в конструктор поведение меняется.
При этом сохраняется исходный интерфейс, что всегда удобно во время тестирования или рефакторинга.
И такое на практике встречается частенько.
По большому счет это все основы, которые стоит четко для себя понимать при работе с репозиторием.
Главное следуем картинке ниже, чтобы Dependency Rule не нарушался и будет вам счастье.
Кстати, стоит ли отдельно рассказать про этот самый Dependency Rule?
Пишите в комменты.
Многие уже знают о таком понятии как репозиторий. Однако, я все равно часто наблюдаю в коде проектов некое непонимание как правильно его "готовить".
Давайте разбираться.
В доках Гугла часто можно встретить примеры в которых во ViewModel напрямую ижектят репозитории, что мягко говоря, не очень правильно.
На самом деле, ViewModel или Presenter (смотря что используете) не должен ничего знать о репозиториях. Обычно с репой работает UseCase или Interactor. И именно он инжектится во ViewModel.
Но давайте о главном.
Что же такое репозиторий (Repository)?
Репозиторий можно рассматривать как коллекцию, которая работает с какой-либо моделью данных.
И только с одной моделью.
Это важно.
Если хочется работать с несколькими моделями, то самое правильное - это создать на каждую модель свой отдельный репозиторий.
Тут не стоит лениться - в дальнейшем будет меньше проблем.
Обычно репозиторий возвращает вам список моделей или конкретную модель, кеширует какие-либо локальные данные и возвращает эти данные из кеша и вот это вот все. Но в нем нет и не должно быть НИКАКОЙ другой логики (особенно бизнес-логики). Чем он тупее - тем лучше. Положили модель куда-то, отдали модель.
Теперь немного про слои.
Самые первые реализации Clean Arctitecture под Android делили слои репозиториев так:
1. Интерфейс репозитория мы помещаем на уровне domain.
2. Реализацию (класс, наследующий интерфейс репозитория) на уровне data.
3. UseCases / Interactors инжектит в конструктор именно интерфейс репозитория, а не реализацию! Это тоже очень важно. Реализацию подставит DI-фреймворк.
Сейчас же, когда проекты стали делить на features (фичи), а еще большие проекты на features в отдельных модулях, принцип разделения на слои остался прежним (в каждом модуле фичи своя структура domain - data - presentation).
Еще стоит отдельно упомянуть, что репозитории могут реализовывать разные DataSource (обычно инжектится в конструктор), например:
class UserRepositoryImpl @Inject constructor(private val source: DataSource): ...
Для чего это нужно?
Ну например, один заинжекченный репозиторий работает с локальным кешем данных, а другой всегда получает данные из сети. Интерфейс репозитория один и тот же, но за счет подстановки нужной реализации DataSource в конструктор поведение меняется.
При этом сохраняется исходный интерфейс, что всегда удобно во время тестирования или рефакторинга.
И такое на практике встречается частенько.
По большому счет это все основы, которые стоит четко для себя понимать при работе с репозиторием.
Главное следуем картинке ниже, чтобы Dependency Rule не нарушался и будет вам счастье.
Кстати, стоит ли отдельно рассказать про этот самый Dependency Rule?
Пишите в комменты.
Забавные новости про GitHub Copilot - AI-помощник программиста.
Первые пользователи поигрались и нашли ряд недостатков, но самое забавное в этом вот что: периодически Copilot вместо нескольких строк кода генерирует цитаты и комментарии из проектов с открытым исходным кодом. Кроме того, он вытаскивает валидные ключи API из репозиториев с открытым исходным кодом разных проектов и выдаёт их пользователям.
С одной стороны хранить открытые ключи - такое, с другой - норм так скрипт получился 🙂
Пока что выдыхаем и работаем дальше - мы все еще нужны.
Детали: https://habr.com/ru/news/t/576228/
Первые пользователи поигрались и нашли ряд недостатков, но самое забавное в этом вот что: периодически Copilot вместо нескольких строк кода генерирует цитаты и комментарии из проектов с открытым исходным кодом. Кроме того, он вытаскивает валидные ключи API из репозиториев с открытым исходным кодом разных проектов и выдаёт их пользователям.
С одной стороны хранить открытые ключи - такое, с другой - норм так скрипт получился 🙂
Пока что выдыхаем и работаем дальше - мы все еще нужны.
Детали: https://habr.com/ru/news/t/576228/
Хабр
Пользователи обнаружили 1170 слов, которые блокирует GitHub Copilot
Пользователи обнаружили в базе нейросетевого помощника программиста GitHub Copilot («второй пилот») 1170 стоп-слов, которые он блокирует при формировании кода. Среди них присутствуют такие...
Играемся с анимациями в Compose
Начал ковырять, как добавить простую анимацию для элементов.
Надо сказать, что вариантов сделать это довольно много.
Для простоты добавим анимацию исчезновения / появления температуры при свайпе (обновлении данных).
Добавить ее можно поместив анимируемый элемент (компонент) внутрь блока Composable-функции (в нашем случае AnimatedVisibility):
По-умолчанию анимация fadeIn() + expandIn() для enter и shrinkOut() + fadeOut() для exit, но в параметрах можно задать любые другие из списка в доке, а через оператор + их еще и можно миксовать, как вы уже догадались.
Пока этот API нужно помечать специальной аннотацией, как экспериментальный. Видимо API еще будет немного меняться, но не до конца понятно, как например, сделать свою длительность анимации (обычно есть какое-нибудь поле duration).
Что ж, будем ковырять дальше.
Ощущение, что возможностей море, но описано это все пока не очень подробно.
Дока: https://developer.android.com/jetpack/compose/animation
Больше примеров здесь: https://medium.com/wizeline-mobile/jetpack-compose-animations-i-f46024bcfa37
и здесь: https://proandroiddev.com/animate-with-jetpack-compose-animate-as-state-and-animation-specs-ffc708bb45f8
Начал ковырять, как добавить простую анимацию для элементов.
Надо сказать, что вариантов сделать это довольно много.
Для простоты добавим анимацию исчезновения / появления температуры при свайпе (обновлении данных).
Добавить ее можно поместив анимируемый элемент (компонент) внутрь блока Composable-функции (в нашем случае AnimatedVisibility):
AnimatedVisibility(
visible = !isRefreshing,
) {
Text(
text = temprature.toString(),
style = Typography.h3
)
}
Анимация в Compose завязана на состояния (как и все остальное, что логично), поэтому мы добавляем поле isRefreshing, которое меняется при swipe to refresh,По-умолчанию анимация fadeIn() + expandIn() для enter и shrinkOut() + fadeOut() для exit, но в параметрах можно задать любые другие из списка в доке, а через оператор + их еще и можно миксовать, как вы уже догадались.
Пока этот API нужно помечать специальной аннотацией, как экспериментальный. Видимо API еще будет немного меняться, но не до конца понятно, как например, сделать свою длительность анимации (обычно есть какое-нибудь поле duration).
Что ж, будем ковырять дальше.
Ощущение, что возможностей море, но описано это все пока не очень подробно.
Дока: https://developer.android.com/jetpack/compose/animation
Больше примеров здесь: https://medium.com/wizeline-mobile/jetpack-compose-animations-i-f46024bcfa37
и здесь: https://proandroiddev.com/animate-with-jetpack-compose-animate-as-state-and-animation-specs-ffc708bb45f8
Продолжаем понемногу копать анимации в Compose.
Один из вариантов, как можно установить длительность - это задать свою анимацию появления или исчезновения (параметры enter / exit).
Зададим, например, анимацию появления:
Вообще, в animationSpec может быть все что угодно, что реализует интерфейс AnimationSpec (неожиданно, да?) и скорее всего намиксовать в этом параметре опций можно много.
Такие дела.
Один из вариантов, как можно установить длительность - это задать свою анимацию появления или исчезновения (параметры enter / exit).
Зададим, например, анимацию появления:
AnimatedVisibility(
visible = !isRefreshing,
enter = fadeIn(animationSpec = tween(1000))
)
Длительность определяется функцией tween(). В нашем случае - 1 секунда.Вообще, в animationSpec может быть все что угодно, что реализует интерфейс AnimationSpec (неожиданно, да?) и скорее всего намиксовать в этом параметре опций можно много.
Такие дела.
Уже пару недель привыкаю / изучаю / назовите как угодно MVI архитектуру presеentation-слоя, которая ляжет на наш новый чудо-UI на базе Compose.
Лучшие статьи скину отдельным постом (посмотрел уже порядка двух десятков, в том числе и для iOS).
Толковых статей не так много, а многие другие обычно состоят из кальки документации по Redux с какими-то своими авторскими особенностями.
Ковыряя существующие решения, буквально вчера случайно увидел этот пост: https://appmattus.medium.com/top-android-mvi-libraries-in-2021-de1afe890f27
Автор сравнивает почти все уже существующие либы для MVI и возможно кому-то будет полезно, когда будете подбирать библиотеку себе.
Но в статье есть два нюанса:
1. В сравнении нет, наверное, самой популярной либы - либы от Badoo - MVICore.
2. Автор в начале утверждает, что свое решение для MVI делать сложно и не очень правильно. Но как по мне, стоит это принимать на веру не до конца, ведь исходя из общей концепции и той же самой доки по Redux - сложного в архитектуре не так много. На первых парах наоборот видится лучшим решением попробовать самостоятельно разобраться в основах и накидать примеры, добавив условно всего несколько интерфейсов, а потом уже перейти на нормальную библиотеку, если это вообще будет нужно. Основная проблема - правильное управление состоянием и разбухание этого самого состояния.
Но об этом позже.
Немного посмотрев на все эти либы, MVICore, Orbit-MVI пока выглядят наиболее адекватными из всех решений.
Есть еще MVIFlow, но она пока еще сыровата.
Поглядим.
Кстати, свои небольшие наброски по MVI сейчас делаю в отдельной ветке: https://github.com/Djangist/ComposeArchSample/tree/feature/udf_mvi
Заглядывайте, пишите под этим постом - пообщаемся.
А чуть позже еще появятся веточки для указанных выше двух либ - для сравнения.
Лучшие статьи скину отдельным постом (посмотрел уже порядка двух десятков, в том числе и для iOS).
Толковых статей не так много, а многие другие обычно состоят из кальки документации по Redux с какими-то своими авторскими особенностями.
Ковыряя существующие решения, буквально вчера случайно увидел этот пост: https://appmattus.medium.com/top-android-mvi-libraries-in-2021-de1afe890f27
Автор сравнивает почти все уже существующие либы для MVI и возможно кому-то будет полезно, когда будете подбирать библиотеку себе.
Но в статье есть два нюанса:
1. В сравнении нет, наверное, самой популярной либы - либы от Badoo - MVICore.
2. Автор в начале утверждает, что свое решение для MVI делать сложно и не очень правильно. Но как по мне, стоит это принимать на веру не до конца, ведь исходя из общей концепции и той же самой доки по Redux - сложного в архитектуре не так много. На первых парах наоборот видится лучшим решением попробовать самостоятельно разобраться в основах и накидать примеры, добавив условно всего несколько интерфейсов, а потом уже перейти на нормальную библиотеку, если это вообще будет нужно. Основная проблема - правильное управление состоянием и разбухание этого самого состояния.
Но об этом позже.
Немного посмотрев на все эти либы, MVICore, Orbit-MVI пока выглядят наиболее адекватными из всех решений.
Есть еще MVIFlow, но она пока еще сыровата.
Поглядим.
Кстати, свои небольшие наброски по MVI сейчас делаю в отдельной ветке: https://github.com/Djangist/ComposeArchSample/tree/feature/udf_mvi
Заглядывайте, пишите под этим постом - пообщаемся.
А чуть позже еще появятся веточки для указанных выше двух либ - для сравнения.
Medium
Top Android MVI libraries in 2021
Comparing redux and MVVM+ style MVI libraries
Либ для MVI / Unidirectional Data Flow становится все больше.
Вот, например, наши любимые ребята из Square, которые подарили нам множество прекрасных библиотек, выпустили свою: https://github.com/square/workflow/
Но прелесть ее в том, что она и для iOS и для Android, в отдельных зависимостях под каждую платформу. Кто-то захочет пойти в кроссплатформу и использовать обе, кто-то возьмет только версию под конкретную платформу. Такое разделение нравится.
Кроме того, либа не выглядит перегруженной своими абстракциями, хотя здесь они тоже чуть отличаются от принятых в том же Redux. Видится, что ее можно использовать как отдельно докрутив свои нужные слои (например, добавив свой Middleware), либо с другими либами.
Вообщем, выглядит любопытно.
Вот, например, наши любимые ребята из Square, которые подарили нам множество прекрасных библиотек, выпустили свою: https://github.com/square/workflow/
Но прелесть ее в том, что она и для iOS и для Android, в отдельных зависимостях под каждую платформу. Кто-то захочет пойти в кроссплатформу и использовать обе, кто-то возьмет только версию под конкретную платформу. Такое разделение нравится.
Кроме того, либа не выглядит перегруженной своими абстракциями, хотя здесь они тоже чуть отличаются от принятых в том же Redux. Видится, что ее можно использовать как отдельно докрутив свои нужные слои (например, добавив свой Middleware), либо с другими либами.
Вообщем, выглядит любопытно.
GitHub
GitHub - square/workflow: A Swift and Kotlin library for making composable state machines, and UIs driven by those state machines.
A Swift and Kotlin library for making composable state machines, and UIs driven by those state machines. - GitHub - square/workflow: A Swift and Kotlin library for making composable state machines,...
Оказывается Text в Compose не поддерживает includeFontPadding: https://issuetracker.google.com/issues/171394808?pli=1
Обходной вариант - создать composable wrapper для TextView: https://stackoverflow.com/questions/66126551/jetpack-compose-centering-text-without-font-padding
Временный костыль.
Так что, сделайте доброе дело - нажмите на звездочку в issue, чтобы Google поправил эту багу в более приоритетном режиме🙂
Обходной вариант - создать composable wrapper для TextView: https://stackoverflow.com/questions/66126551/jetpack-compose-centering-text-without-font-padding
Временный костыль.
Так что, сделайте доброе дело - нажмите на звездочку в issue, чтобы Google поправил эту багу в более приоритетном режиме🙂
Stack Overflow
Jetpack Compose, centering text without font padding?
I'm struggling with vertically centering text in Jetpack Compose version alpha-11. It appears that my font has a significant amount of padding and I'm unable to find a way to disable it. This has c...
Пост 21. Немного о юнит-тестировании на примере Spek 2.
Устали от постов про Compose? Отлично!
Поговорим о тестировании.
У нас в банке для юнит-тестов используется фреймворк Spek: https://github.com/spekframework/spek/ который позволяет писать тесты в виде спецификаций, что вероятно, более наглядно для читающего.
Но сперва пару слов зачем вообще тестирование? В частности, юнит-тестирование.
Немного отсебятины, на примере нескольких компаний:
1. Отловить часть проблем, которые могут попасть в релиз. Условно CI собирая сборку, прогоняет тесты, и мы можем увидеть, что отвалилось или где закралась ошибка, когда тесты упадут.
2. Качество кода и в итоге продукта. Но здесь стоит учитывать code coverage - процент кода, покрытого тестами. Чем он выше - тем лучше.
3. Через тестирование ведут разработку, например TDD. Пишут сперва тесты, которые валятся и потом пишут код, который позволит пройти эти тесты.
4. Юнит-тесты прогоняются локально, на машине. Не нужно запускать эмулятор или иметь ферму устройств в облаке. Таким образом можно быстро протестировать бизнес-логику.
В этой часте мы настроим либу и напишем немного простых примеров.
Добавляем в app/build.gradle строки:
Теперь нам осталось добавить classpath этого плагина в корневом build.gradle:
Он позволит нам запускать тесты через контекстное меню, нажав на нужную папку. Но также добавляет зеленую кнопку запуска у каждого теста.
Ура, мы вроде все настроили.
Теперь простенький примерчик.
Допустим у нас есть утилита, которую нужно протестировать:
1. describe описывает группу тестов, как в нашем случае, тесты для сложения и вычитания.
2. It описывает конкретный тест. Внутри него как раз пишут разные выражения (Spek кстати не включает какие-то свои assert-выражения, можно лишь воспользоваться jUnit-овскими),
Но сам DSL разделен на две части: specification и gherkin. У нас как раз первая.
При запуске мы получим примерно то, что на скриншоте ниже. Как видите, можно даже посмотреть дополнительную инфу о проваленных тестах.
Таким образом мы протестировали наш Calculator и его ожидаемое поведение. Проваленные тесты здесь для примера, но иногда пишут и такие тоже.
Если нужен пример целиком - напишите в коммент, закину отдельной репой.
Пост получился и так длинным, поэтому на этом и закончим.
Устали от постов про Compose? Отлично!
Поговорим о тестировании.
У нас в банке для юнит-тестов используется фреймворк Spek: https://github.com/spekframework/spek/ который позволяет писать тесты в виде спецификаций, что вероятно, более наглядно для читающего.
Но сперва пару слов зачем вообще тестирование? В частности, юнит-тестирование.
Немного отсебятины, на примере нескольких компаний:
1. Отловить часть проблем, которые могут попасть в релиз. Условно CI собирая сборку, прогоняет тесты, и мы можем увидеть, что отвалилось или где закралась ошибка, когда тесты упадут.
2. Качество кода и в итоге продукта. Но здесь стоит учитывать code coverage - процент кода, покрытого тестами. Чем он выше - тем лучше.
3. Через тестирование ведут разработку, например TDD. Пишут сперва тесты, которые валятся и потом пишут код, который позволит пройти эти тесты.
4. Юнит-тесты прогоняются локально, на машине. Не нужно запускать эмулятор или иметь ферму устройств в облаке. Таким образом можно быстро протестировать бизнес-логику.
В этой часте мы настроим либу и напишем немного простых примеров.
Добавляем в app/build.gradle строки:
testImplementation 'org.junit.platform:junit-platform-engine:1.6.2'
testImplementation 'org.spekframework.spek2:spek-dsl-jvm:2.0.9'
testImplementation 'org.spekframework.spek2:spek-runner-junit5:2.0.9'
Добавляем плагин в секцию plugins:id "de.mannodermaus.android-junit5"
В секцию android добавляем опции:testOptions {
junitPlatform {
filters {
engines {
include 'spek2'
}
}
}
unitTests.all {
testLogging.events = ["passed", "skipped", "failed"]
}
}
Плагин, который мы добавили нужен для запуска тестов на jUnit 5. Теперь нам осталось добавить classpath этого плагина в корневом build.gradle:
classpath "de.mannodermaus.gradle.plugins:android-junit5:1.8.0.0"
Для запуска тестов в Android Studio мы будем использовать официальный плагин: https://plugins.jetbrains.com/plugin/10915-spek-frameworkОн позволит нам запускать тесты через контекстное меню, нажав на нужную папку. Но также добавляет зеленую кнопку запуска у каждого теста.
Ура, мы вроде все настроили.
Теперь простенький примерчик.
Допустим у нас есть утилита, которую нужно протестировать:
class Calculator {
fun add(num1:Int, num2: Int) = num1 + num2
}
Напишем нашу спецификацию (группу тестов, объединенных одной идеей):object CalculatorTest : Spek({
val calc = Calculator()
describe("add operation tests") {
it("add test") {
assertEquals(3, calc.add(1,2))
}
it("add test failure") {
assertEquals(4, calc.add(1,2))
}
it("add test another") {
assertEquals(2+1, calc.add(2,1))
}
}
})
Довольно читабельно, как думаете?1. describe описывает группу тестов, как в нашем случае, тесты для сложения и вычитания.
2. It описывает конкретный тест. Внутри него как раз пишут разные выражения (Spek кстати не включает какие-то свои assert-выражения, можно лишь воспользоваться jUnit-овскими),
Но сам DSL разделен на две части: specification и gherkin. У нас как раз первая.
При запуске мы получим примерно то, что на скриншоте ниже. Как видите, можно даже посмотреть дополнительную инфу о проваленных тестах.
Таким образом мы протестировали наш Calculator и его ожидаемое поведение. Проваленные тесты здесь для примера, но иногда пишут и такие тоже.
Если нужен пример целиком - напишите в коммент, закину отдельной репой.
Пост получился и так длинным, поэтому на этом и закончим.
Небольшой лайфхак.
У вас никогда не было задачи выпилить из релизного билда все логи?
Особенно интересной она становится, если у вас в проекте по каким-то причинам все еще не используется Timber (мало ли, проект старый).
Если нет никакой обертки вокруг android.util.Log то задачка та еще.
А вот решить ее можно довольно просто, используя Proguard и добавив в конфиг строки:
Данный конфиг выпилит из релизного кода все вызовы Log.d / Log.v.
Это, конечно, не отменяет нужды по замене android.util.Log на Timber, но времени сэкономит много. А самое главное, этот способ будет работать и для других случаев.
Но с конфигами ProGuard всегда нужно быть осторожным!
Шаг вправо, шаг влево - краш релизной сборки!
У вас никогда не было задачи выпилить из релизного билда все логи?
Особенно интересной она становится, если у вас в проекте по каким-то причинам все еще не используется Timber (мало ли, проект старый).
Если нет никакой обертки вокруг android.util.Log то задачка та еще.
А вот решить ее можно довольно просто, используя Proguard и добавив в конфиг строки:
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
}
Данный конфиг выпилит из релизного кода все вызовы Log.d / Log.v.
Это, конечно, не отменяет нужды по замене android.util.Log на Timber, но времени сэкономит много. А самое главное, этот способ будет работать и для других случаев.
Но с конфигами ProGuard всегда нужно быть осторожным!
Шаг вправо, шаг влево - краш релизной сборки!
Вдруг кому-то пригодится!
Shimmer effect для Compose: https://github.com/valentinilk/compose-shimmer
Как раз прикручиваю себе, работает!
Shimmer effect для Compose: https://github.com/valentinilk/compose-shimmer
Как раз прикручиваю себе, работает!
GitHub
GitHub - valentinilk/compose-shimmer: A simple shimmer library for Jetpack Compose.
A simple shimmer library for Jetpack Compose. Contribute to valentinilk/compose-shimmer development by creating an account on GitHub.
Небольшой пример как сделать drag & drop-элементы списка в Compose с использованием библиотеки org.burnoutcrew.composereorderable:reorderable:
https://www.rockandnull.com/jetpack-compose-drag-and-drop-list-reorder/
Репозиторий либы: https://github.com/aclassen/ComposeReorderable
https://www.rockandnull.com/jetpack-compose-drag-and-drop-list-reorder/
Репозиторий либы: https://github.com/aclassen/ComposeReorderable
Rock and Null
Jetpack Compose: Drag-and-drop reorder for lists
Some features that you would otherwise expect are not yet implemented in Jetpack Compose: One such feature: drag-and-drop reordering for lists.