Продолжаем понемногу копать анимации в 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.
Пост 22. Orbit-MVI. Первое впечатление.
Попробовал https://github.com/orbit-mvi/orbit-mvi в качестве механизма управления состоянием для Compose (на самом деле можно использовать и вместе с фрагментами).
Впечатления пока приятные.
Ничего лишнего, по большому счету библиотека представляет собой контейнер с механизмом управления состоянием. Эдакий аналог того как бы вы управляли состоянием сами (через StateFlow, Flow), но все спрятано (почти все) под капотом. Код в целом становится почище.
Теперь ViewModel главного экрана выглядит так:
Как вам? Мне лично нравится такая простота. Да, у нас простенький пример, но все же!
Теперь немного пояснений.
1. Все состояние хранится в переменной container, которая обычно состоит из пары State, SideEffect. Обычно под Side Effect подразумевается показ тоаста, навигация на другой экран и вот такие вещи.
2. Как видите, стейт по-прежнему представляет из себя StateFlow<YourState>. То бишь, работа со стейтом просто немного спрятана и не нужно писать кучу переменных во ViewModel.
3. Как и раньше, ViewModel у нас представляет из себя Store (в терминах Redux, сущность, которая хранит состояние), но теперь мы наследуемся от библиотечного интерфейса ContainerHost<MainState, MainSideEffect> передавая в него состояние нашего экрана и сайд эффект. Интерфейс как раз и вынуждает нас переопределять переменную container.
4. Функция container<State, SideEffect>() принимает также блок, который выполнится при первом обращении к переменной. В нашем случае, удобно поместить в этот блок первую загрузку погодных данных.
5. Намерение (в терминах Redux) - это некое действие, обычно пользовательское. Обычно каждое намерение меняет стейт (копируя его, а не переопределяя) через вызов функции redux. Собственно, у нас все также. В блоке intent мы можем сделать какие-либо действия для получения данных, в скоупе корутин, а в самой функции redux мы лишь копируем нужные нам данные в новый стейт через функцию copy, которая доступна для data-классов. Тем самым у нас immutable-стейт, что как раз по канонам Unidirectional Data Flow.
Со стороны Compose все взаимодействие с состоянием выглядит так:
Со стороны Compose особо ничего не поменялось. В переменной container как раз и хранится состояние, а эффекты в container.sideEffectFlow.
Так или иначе, подобные архитектуры скоро станут мейнстримом в мобайле!
А вы что думаете про это все?
Попробовал https://github.com/orbit-mvi/orbit-mvi в качестве механизма управления состоянием для Compose (на самом деле можно использовать и вместе с фрагментами).
Впечатления пока приятные.
Ничего лишнего, по большому счету библиотека представляет собой контейнер с механизмом управления состоянием. Эдакий аналог того как бы вы управляли состоянием сами (через StateFlow, Flow), но все спрятано (почти все) под капотом. Код в целом становится почище.
Теперь ViewModel главного экрана выглядит так:
@HiltViewModel
class MainViewModel @Inject constructor(
private val interactor: WeatherInteractor
) : ViewModel(), ContainerHost<MainState, MainSideEffect> {
override val container = container<MainState, MainSideEffect>(MainState()) {
loadWeather()
}
private fun loadWeather(fromRefresh: Boolean = false) = intent {
val mainWeather = interactor.getMainWeatherData(fromRefresh)
val daysWeather = interactor.getDaysWeatherData(fromRefresh)
val hoursWeather = interactor.getHoursWeatherData(fromRefresh)
reduce {
state.copy(
mainWeather = mainWeather,
daysWeather = daysWeather,
hoursWeather = hoursWeather,
isRefreshing = false
)
}
}
fun pullToRefresh() = intent {
reduce {
state.copy(isRefreshing = true)
}
delay(3000) //TODO just for sample
loadWeather(fromRefresh = true)
}
}
Как вам? Мне лично нравится такая простота. Да, у нас простенький пример, но все же!
Теперь немного пояснений.
1. Все состояние хранится в переменной container, которая обычно состоит из пары State, SideEffect. Обычно под Side Effect подразумевается показ тоаста, навигация на другой экран и вот такие вещи.
2. Как видите, стейт по-прежнему представляет из себя StateFlow<YourState>. То бишь, работа со стейтом просто немного спрятана и не нужно писать кучу переменных во ViewModel.
3. Как и раньше, ViewModel у нас представляет из себя Store (в терминах Redux, сущность, которая хранит состояние), но теперь мы наследуемся от библиотечного интерфейса ContainerHost<MainState, MainSideEffect> передавая в него состояние нашего экрана и сайд эффект. Интерфейс как раз и вынуждает нас переопределять переменную container.
4. Функция container<State, SideEffect>() принимает также блок, который выполнится при первом обращении к переменной. В нашем случае, удобно поместить в этот блок первую загрузку погодных данных.
5. Намерение (в терминах Redux) - это некое действие, обычно пользовательское. Обычно каждое намерение меняет стейт (копируя его, а не переопределяя) через вызов функции redux. Собственно, у нас все также. В блоке intent мы можем сделать какие-либо действия для получения данных, в скоупе корутин, а в самой функции redux мы лишь копируем нужные нам данные в новый стейт через функцию copy, которая доступна для data-классов. Тем самым у нас immutable-стейт, что как раз по канонам Unidirectional Data Flow.
Со стороны Compose все взаимодействие с состоянием выглядит так:
@Composable
fun MainScreen() {
val viewModel: MainViewModel = hiltViewModel()
val state = viewModel.container.stateFlow.collectAsState().value
ShowWeather(
viewModel,
state.mainWeather,
state.daysWeather,
state.hoursWeather
)
}
Со стороны Compose особо ничего не поменялось. В переменной container как раз и хранится состояние, а эффекты в container.sideEffectFlow.
Так или иначе, подобные архитектуры скоро станут мейнстримом в мобайле!
А вы что думаете про это все?
GitHub
GitHub - orbit-mvi/orbit-mvi: A simple MVI framework for Kotlin Multiplatform and Android
A simple MVI framework for Kotlin Multiplatform and Android - orbit-mvi/orbit-mvi
Друзья, тут такое дело.
В свободное время ковыряю потихоньку iOS и еще медленнее Flutter. Но все же!
Было бы вам интересно почитать что-нибудь про них и, например, про KMM?
В свободное время ковыряю потихоньку iOS и еще медленнее Flutter. Но все же!
Было бы вам интересно почитать что-нибудь про них и, например, про KMM?
Final Results
74%
Да
26%
Нет, интересен только Android
Что новенького в рассылках #6
Забавная либа: https://github.com/kojofosu/SplitButton
Функционал чем-то напоминает улучшенную версию Spinner-ов. Для сложных экранов может пригодиться.
Либа для роутинга: https://github.com/Zhuinden/simple-stack/
Выглядит не так плохо, как большинство, но вцелом можно подождать и свежую библиотеку от автора Cicherone, или юзать Navigation Library из JetPack.
Вышел Realm Kotlin 0.6.0: https://medium.com/realm/realm-kotlin-0-6-0-baa26dcbbb9
Либа для конвертации разных форматов в виде DSL-ки с разными операторами: https://github.com/nomemory/mapneat
Интересная часть - конвертация POJO в JSON. Может пригодится, правда под капотом дофига всего тянет.
Демка по KMM: https://github.com/joreilly/FantasyPremierLeague
Как-нибудь ее разберем с вами.
В качества UI: Compose и SwiftUI.
Кстати, кто не в курсе. SwiftUI во многом аналогичный Compose UI-тулкит для iOS, но появился (если не путаю) аж в 2018-ом! Так что, разобравшись с Compose будет проще понимать в будущем и SwiftUI. А вот preview функций SwiftUI в XCode работает пока гораздо стабильнее, чем аналогичная история для Compose в Android Studio.
Либа для создания разных фабрик: https://github.com/bluegroundltd/kfactory
Во многом нужна для более удобного тестирования. Помогает подготовить фабрики данных для моделей, которые потом можно использовать в Unit-тестах.
Напоследок.
Roadmap по изучению Compose:
https://victorbrandalise.com/roadmap-for-jetpack-compose/
Забавная либа: https://github.com/kojofosu/SplitButton
Функционал чем-то напоминает улучшенную версию Spinner-ов. Для сложных экранов может пригодиться.
Либа для роутинга: https://github.com/Zhuinden/simple-stack/
Выглядит не так плохо, как большинство, но вцелом можно подождать и свежую библиотеку от автора Cicherone, или юзать Navigation Library из JetPack.
Вышел Realm Kotlin 0.6.0: https://medium.com/realm/realm-kotlin-0-6-0-baa26dcbbb9
Либа для конвертации разных форматов в виде DSL-ки с разными операторами: https://github.com/nomemory/mapneat
Интересная часть - конвертация POJO в JSON. Может пригодится, правда под капотом дофига всего тянет.
Демка по KMM: https://github.com/joreilly/FantasyPremierLeague
Как-нибудь ее разберем с вами.
В качества UI: Compose и SwiftUI.
Кстати, кто не в курсе. SwiftUI во многом аналогичный Compose UI-тулкит для iOS, но появился (если не путаю) аж в 2018-ом! Так что, разобравшись с Compose будет проще понимать в будущем и SwiftUI. А вот preview функций SwiftUI в XCode работает пока гораздо стабильнее, чем аналогичная история для Compose в Android Studio.
Либа для создания разных фабрик: https://github.com/bluegroundltd/kfactory
Во многом нужна для более удобного тестирования. Помогает подготовить фабрики данных для моделей, которые потом можно использовать в Unit-тестах.
Напоследок.
Roadmap по изучению Compose:
https://victorbrandalise.com/roadmap-for-jetpack-compose/
GitHub
GitHub - kojofosu/SplitButton: A dual-function menu button that offers a default action as well as the possibility of choosing…
A dual-function menu button that offers a default action as well as the possibility of choosing a different action by selecting from a set of alternatives. - GitHub - kojofosu/SplitButton: A dual-...
Если готовитесь к собесу - 50 вопросов с ответами:
https://code.coursesity.com/android-interview-questions
Вопросы скорее для подготовки на позицию джуна или начинающего миддла максимум.
https://code.coursesity.com/android-interview-questions
Вопросы скорее для подготовки на позицию джуна или начинающего миддла максимум.
Coursesity
Top 50 Android Interview Questions For Android Developer Jobs
If you are preparing for an Android developer interview, this article contains the top 50 Android Interview Questions & Answers to help you out.
Тут сравнили скорость компиляции Android-проектов на:
1. 2021 14" MacBook Pro — M1 Pro (10 core)— 32gb RAM
2. Desktop (Pop_OS!) — 4.2ghz AMD 2950x (16 core) — 64gb RAM
3. 2019 16" MacBook Pro — 2.4ghz Intel i9 (8 core)–32gb RAM
И M1 Pro показал себя очень и очень неплохо, проиграв ДЕСКТОПУ лишь на Clean Build!
Впечатляет!
1. 2021 14" MacBook Pro — M1 Pro (10 core)— 32gb RAM
2. Desktop (Pop_OS!) — 4.2ghz AMD 2950x (16 core) — 64gb RAM
3. 2019 16" MacBook Pro — 2.4ghz Intel i9 (8 core)–32gb RAM
И M1 Pro показал себя очень и очень неплохо, проиграв ДЕСКТОПУ лишь на Clean Build!
Впечатляет!
Medium
The M1 Pro for Android Engineers
How well do the new M1 Pro MacBooks stack up for Android Development?
Поигрался немного с созданием, запуском проектов на Flutter и KMM.
1. Для обеих платформ предлагается плагин под Android Studio. И нужно сказать, под Flutter он пока выглядит интереснее. Например, есть возможность открыть каждый специфичный модуль либо в Xcode, либо в отдельном экземпляре Android Studio (AS). А вот у плагина для KMM такого нет.
2. Flutter-проект запустился на обеих платформах, и даже в Chrome, что несомненно круто! (но стоит сказать, что когда AS не видит симулятор iOS, приходится запускать проект из Xcode, благо это делается быстро через контекстное меню в AS).
3. KMM-проект так и не запустился на iOS. И, к слову, почему-то при настройке проекта в мастере, не выбирается нужный тул из доки (Xcode Build phases). Вообщем, тут нужно явно посидеть и понастраивать.
4. Flutter-овский Hot Reload - это не что! Как будто вы кодите веб-приложение. Что-то поменял в коде (например, текст заголовка) и тут же изменения видны в эмуляторе/симуляторе, без всяких Clean-Build и повторных запусков. Кайф!
5. Помните open source проект на KMM - https://github.com/joreilly/FantasyPremierLeague ? Думаю, попробую импортнуть в AS и запустить. Вдруг запустится нормально на iOS. Но… проект будто не узнает эту структуру и считает его обычным проектом под Android. И на Android-эмуляторе запустился (правда крашнулся после нажатия на Поиск, но не суть). А вот то, что его можно запустить под iOS, видимо, знает только автор этого проекта. AS не видит даже подобной настройки. Возможно, он был создан по другой структуре и новый плагин для KMM ее не понимает. Вообщем, тоже не вышло - нужны пляски с бубном и здесь.
6. Но дока, кстати, для обеих платформ (по крайней мере, чтобы быстро стартануть) более-менее. Под KMM чуть хуже, но есть отдельные гайды по запуску проекта под iOS и там, так что попробуем победить эту проблему.
Вообщем, Flutter, пока выглядит интереснее. Но немного отталкивает сам язык Dart - какие никакие отличия в нем между Java / Kotlin есть, хотя с виду он не очень сложный.
У KMM общие части можно писать на Kotlin, что для нас плюс, а какие-то платформенно-специфичные уже открывать в Xcode и кодить на Swift.
1. Для обеих платформ предлагается плагин под Android Studio. И нужно сказать, под Flutter он пока выглядит интереснее. Например, есть возможность открыть каждый специфичный модуль либо в Xcode, либо в отдельном экземпляре Android Studio (AS). А вот у плагина для KMM такого нет.
2. Flutter-проект запустился на обеих платформах, и даже в Chrome, что несомненно круто! (но стоит сказать, что когда AS не видит симулятор iOS, приходится запускать проект из Xcode, благо это делается быстро через контекстное меню в AS).
3. KMM-проект так и не запустился на iOS. И, к слову, почему-то при настройке проекта в мастере, не выбирается нужный тул из доки (Xcode Build phases). Вообщем, тут нужно явно посидеть и понастраивать.
4. Flutter-овский Hot Reload - это не что! Как будто вы кодите веб-приложение. Что-то поменял в коде (например, текст заголовка) и тут же изменения видны в эмуляторе/симуляторе, без всяких Clean-Build и повторных запусков. Кайф!
5. Помните open source проект на KMM - https://github.com/joreilly/FantasyPremierLeague ? Думаю, попробую импортнуть в AS и запустить. Вдруг запустится нормально на iOS. Но… проект будто не узнает эту структуру и считает его обычным проектом под Android. И на Android-эмуляторе запустился (правда крашнулся после нажатия на Поиск, но не суть). А вот то, что его можно запустить под iOS, видимо, знает только автор этого проекта. AS не видит даже подобной настройки. Возможно, он был создан по другой структуре и новый плагин для KMM ее не понимает. Вообщем, тоже не вышло - нужны пляски с бубном и здесь.
6. Но дока, кстати, для обеих платформ (по крайней мере, чтобы быстро стартануть) более-менее. Под KMM чуть хуже, но есть отдельные гайды по запуску проекта под iOS и там, так что попробуем победить эту проблему.
Вообщем, Flutter, пока выглядит интереснее. Но немного отталкивает сам язык Dart - какие никакие отличия в нем между Java / Kotlin есть, хотя с виду он не очень сложный.
У KMM общие части можно писать на Kotlin, что для нас плюс, а какие-то платформенно-специфичные уже открывать в Xcode и кодить на Swift.
Нельзя жить с разбитыми окнами!
Во время чтения книги "Программист-прагматик" запомнился довольно интересный принцип, который действительно встречается в долгоиграющих проектах.
Далее, интересные фрагменты из книги.
—
Имеется немало факторов, способствующих деградации программного обеспечения, и самый важный из них, по-видимому, имеет отношение к психологии или культуре в работе над проектом. Какими бы совершенными ни были составленные планы или участники проекта, он все равно может быть подвергнут деградации и разрушению в течении всего срока своего действия. Тем не менее существуют и такие проекты, которые успешно противостоят естественной тенденции к беспорядку и ухитряются завершиться вполне удачно, несмотря на огромные трудности и постоянные задержки и помехи.
В чем же тогда отличия? В старых кварталах городов одни здания красивы и чисты, тогда как другие выглядят как трухлявые развалины. Исследователи в сфере преступности и упадка городов открыли замечательный пусковой механизм, очень быстро превращающий чистое, нетронутое, нежилое здание в разрушенную и заброшенную трущобу. Это разбитое окно.
Оказывается, что если не отремонтировать хотя бы одно разбитое окно в течении какого-нибудь значительного периода времени, у жильцов возникнет ощущение заброшенности здания, а следовательно, отсутствия заботы городских властей о его состоянии. И, как следствие, разбивается еще одно окно, а люди начинают захламлять здание всяким мусором. В течении относительно короткого периода времени здание разрушается настолько, что у его владельца не возникает никакого желания отремонтировать его, и тогда ощущение заброшенности становится вполне реальным.
Как показывают исследования, безнадежность оказывается заразной.
Совет: "Нельзя жить с разбитыми окнами"
По существу, означает, что нельзя мириться с неудачными проектными решениями или плохо написанными фрагментами кода, оставляя их неисправленными. Исправляйте их, как только обнаружите. Если же времени на исправление недостаточно, закомментируйте неисправный код, выведите сообщение "Не реализовано" или же подставьте вместо него фиктивные данные подобно тому, как временно заколачивают разбитые окна. Словом, предпримите какое-то действие, чтобы предотвратить дальнейший ущерб и тем самым показать, что владеете ситуацией.
Еще один совет из этой же серии: прежде всего не навредить!
Присоединившись к проекту, где код безупречен, вам следует действовать крайне осторожно, чтобы не навредить.
—
А вы встречали подобное в своей практике?
Во время чтения книги "Программист-прагматик" запомнился довольно интересный принцип, который действительно встречается в долгоиграющих проектах.
Далее, интересные фрагменты из книги.
—
Имеется немало факторов, способствующих деградации программного обеспечения, и самый важный из них, по-видимому, имеет отношение к психологии или культуре в работе над проектом. Какими бы совершенными ни были составленные планы или участники проекта, он все равно может быть подвергнут деградации и разрушению в течении всего срока своего действия. Тем не менее существуют и такие проекты, которые успешно противостоят естественной тенденции к беспорядку и ухитряются завершиться вполне удачно, несмотря на огромные трудности и постоянные задержки и помехи.
В чем же тогда отличия? В старых кварталах городов одни здания красивы и чисты, тогда как другие выглядят как трухлявые развалины. Исследователи в сфере преступности и упадка городов открыли замечательный пусковой механизм, очень быстро превращающий чистое, нетронутое, нежилое здание в разрушенную и заброшенную трущобу. Это разбитое окно.
Оказывается, что если не отремонтировать хотя бы одно разбитое окно в течении какого-нибудь значительного периода времени, у жильцов возникнет ощущение заброшенности здания, а следовательно, отсутствия заботы городских властей о его состоянии. И, как следствие, разбивается еще одно окно, а люди начинают захламлять здание всяким мусором. В течении относительно короткого периода времени здание разрушается настолько, что у его владельца не возникает никакого желания отремонтировать его, и тогда ощущение заброшенности становится вполне реальным.
Как показывают исследования, безнадежность оказывается заразной.
Совет: "Нельзя жить с разбитыми окнами"
По существу, означает, что нельзя мириться с неудачными проектными решениями или плохо написанными фрагментами кода, оставляя их неисправленными. Исправляйте их, как только обнаружите. Если же времени на исправление недостаточно, закомментируйте неисправный код, выведите сообщение "Не реализовано" или же подставьте вместо него фиктивные данные подобно тому, как временно заколачивают разбитые окна. Словом, предпримите какое-то действие, чтобы предотвратить дальнейший ущерб и тем самым показать, что владеете ситуацией.
Еще один совет из этой же серии: прежде всего не навредить!
Присоединившись к проекту, где код безупречен, вам следует действовать крайне осторожно, чтобы не навредить.
—
А вы встречали подобное в своей практике?
👍1
Красота!
Скоро дизайн под Android будут делать дизайнеры в прямом смысле этого слова🙂
https://www.figma.com/community/plugin/856651176156241740/Figma-to-Compose
Осталось им добавить в работу "чуточку" системности.
Скоро дизайн под Android будут делать дизайнеры в прямом смысле этого слова🙂
https://www.figma.com/community/plugin/856651176156241740/Figma-to-Compose
Осталось им добавить в работу "чуточку" системности.
Figma
Figma to Compose | Figma Community
Figma Community plugin - (beta, code won't exactly match input)
Easily convert Figma designs directly to Kotlin code for Jetpack Compose. (Not affiliated with either of them)
Often times designs contain non-repeated distances, colours and proportions. Replicating…
Easily convert Figma designs directly to Kotlin code for Jetpack Compose. (Not affiliated with either of them)
Often times designs contain non-repeated distances, colours and proportions. Replicating…
Telegram теперь встраивает собственные рекламные посты в каналах. Они все отмечены знаком «Спонсировано». Поэтому, если увидите такой пост (мало ли!) — знайте, влиять на спонсорский контент от Telegram авторы каналов пока никак не могут, а, следовательно, не могут отвечать за достоверность информации и ее качество.
Будьте аккуратны!
Будьте аккуратны!
Старый Мобильщик pinned «Telegram теперь встраивает собственные рекламные посты в каналах. Они все отмечены знаком «Спонсировано». Поэтому, если увидите такой пост (мало ли!) — знайте, влиять на спонсорский контент от Telegram авторы каналов пока никак не могут, а, следовательно,…»
Небольшой пример Bottom Sheet на Compose
В сети можно встретить, например, такой вариант реализации Bottom Sheets:
https://proandroiddev.com/how-to-master-swipeable-and-nestedscroll-modifiers-in-compose-bb0635d6a760
Но, по-моему, он полезен скорее, как пример работы с разными модификаторами, а также показывает, что можно довольно просто создавать свои компоненты.
А вот для Bottom Sheets есть готовое решение, основанное на BottomSheetScaffold:
И да, список внутри Bottom Sheet прекрасно работает. Хотя тут он довольно простой.
Теперь нам нужна кнопка, которая будет менять состояние нашего Bottom Sheet (параметр bottomSheetScaffoldState) и в зависимости от него он и будет показываться / скрываться.
Все довольно просто.
Но самое интересное, что для Bottom Sheet можно даже указать Top Bar, Floating Action Button и кучу других вещей (например, цвет контента, цвет шита и т.д.). Все потому что его реализовали как обычный Scaffold, который также имеет заготовку так называемых слотов: https://developer.android.com/jetpack/compose/layouts/basics#slot-based-layouts.
Почему они так сделали с Bottom Sheet не очень понятно, но возможность такая есть.
В сети можно встретить, например, такой вариант реализации Bottom Sheets:
https://proandroiddev.com/how-to-master-swipeable-and-nestedscroll-modifiers-in-compose-bb0635d6a760
Но, по-моему, он полезен скорее, как пример работы с разными модификаторами, а также показывает, что можно довольно просто создавать свои компоненты.
А вот для Bottom Sheets есть готовое решение, основанное на BottomSheetScaffold:
@ExperimentalMaterialApi
@Composable
fun BottomSheet(bottomSheetScaffoldState: BottomSheetScaffoldState) {
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetContent = {
LazyColumn {
items(6) {
Text("$it")
}
}
},
sheetPeekHeight = 0.dp,
sheetBackgroundColor = Color.Gray
){}
}
И да, список внутри Bottom Sheet прекрасно работает. Хотя тут он довольно простой.
Теперь нам нужна кнопка, которая будет менять состояние нашего Bottom Sheet (параметр bottomSheetScaffoldState) и в зависимости от него он и будет показываться / скрываться.
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
)
...
Button(onClick = {
coroutineScope.launch {
if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) {
bottomSheetScaffoldState.bottomSheetState.expand()
} else {
bottomSheetScaffoldState.bottomSheetState.collapse()
}
}
}) {
Text(text = "Show/Hide BottomSheet")
}
Все довольно просто.
Но самое интересное, что для Bottom Sheet можно даже указать Top Bar, Floating Action Button и кучу других вещей (например, цвет контента, цвет шита и т.д.). Все потому что его реализовали как обычный Scaffold, который также имеет заготовку так называемых слотов: https://developer.android.com/jetpack/compose/layouts/basics#slot-based-layouts.
Почему они так сделали с Bottom Sheet не очень понятно, но возможность такая есть.
Тут оказывается в августе вышло потенциально интересное чтиво от команды Android:
https://chethaase.medium.com/androids-765c803d5ff6
Chet Haase в своей книге, которую он писал 4 года, расскажет почему Android стал таким успешным, как это было, как шла разработка и о команде, которая нам подарила сие чудо.
Кстати, хороший вариант на подарок.
Правда на русском пока не встречал или таки уже есть?
https://chethaase.medium.com/androids-765c803d5ff6
Chet Haase в своей книге, которую он писал 4 года, расскажет почему Android стал таким успешным, как это было, как шла разработка и о команде, которая нам подарила сие чудо.
Кстати, хороший вариант на подарок.
Правда на русском пока не встречал или таки уже есть?
Medium
Androids
A Book is Born