Если у вас бесконечно крашится приложение Google - вы не одиноки!
Нужно зайти в Приложения -> Google -> тапнуть на троеточие - > Удалить обновления.
Проверил на себе - работает! А то уже хотелось свой Pixel 3a в окно выкинуть.
И да, мы тут над нашим банком иногда угараем, что фичи никто не тестирует, а тут такой фейл от Google.
Вообщем, тестирование, тестирование и еще раз тестирование.
Детали: https://www.androidauthority.com/google-app-crashing-1237738/
Нужно зайти в Приложения -> Google -> тапнуть на троеточие - > Удалить обновления.
Проверил на себе - работает! А то уже хотелось свой Pixel 3a в окно выкинуть.
И да, мы тут над нашим банком иногда угараем, что фичи никто не тестирует, а тут такой фейл от Google.
Вообщем, тестирование, тестирование и еще раз тестирование.
Детали: https://www.androidauthority.com/google-app-crashing-1237738/
Android Authority
Is your Google app crashing? You're not alone. (Updated: Fix incoming)
Is the Google app crashing on your phone? You are not alone. An update to the app seems to be causing the problem. Here's how you can fix it.
Эксперименты с Compose. Часть 2.
Продолжаем разбираться с Compose.
Впечатления стали более приятными.
Интерфейс приложения стал более интересным на вид и заметил, что во время "верстки" стал чаще пользоваться Preview.
Добавил горизонтальный (LazyRow) и вертикальный список (LazyColumn).
Прелесть в том, что не нужны больше адаптеры, но интересно будет рассмотреть пример с обновлением данных (кейс в котором часто используется DiffUtils, чтобы не обновлять список целиком).
Небольшой пример добавления списка элементов:
Вообще, многие вещи в Compose настолько интуитивны, что описывать их особо нет смысла. Как-то все само собой получается, даже без документации, которая все еще скудновата. Но это больше актуально для каких-то базовых вещей.
В корневом элементе теперь SwipeToRefresh, который вынесен почему-то в отдельную библиотеку Accompanist и вам нужно дополнительно ее подключать.
С этим элементом есть проблема. Сам жест свайп ту рефреш сейчас работает только где пустое пространство, а не поверх всего макета как обычно и должно быть. Придется скорее всего немного подправить макет. Есть вариант использовать модификатор nestedscroll, но пока примеров с ним особо не нашлось. А если добавить в Column модификатор verticallscroll все крашится. Вообщем, пока все со свайп ту рефреш сыровато.
Кстати, в Appcompanist еще есть и Pager (аналог ViewPager), Insets, расширения работы с библиотеками загрузки изображений (Coil и Glide) и Permissions. Скорее всего список утилит в этой либе будет расширяться.
Разобрался с ConstraintLayout. Его гибкость по-прежнему с нами, но задавать все констрейнты в коде, конечно, не привычно:
Управляет зависимостями Hilt. С ним чуть пришлось повозиться (баг с Gradle из поста выше). В итоге обошлось без передачи экземпляра вьюмодели в функцию, хотя сами вьюмодели можно получать и в скоупе composable-функции.
А в следующий раз поговорим про states (состояния), анимации и сделаем наш экран еще живее.
Репозиторий: https://github.com/Djangist/ComposeArchSample
Продолжаем разбираться с Compose.
Впечатления стали более приятными.
Интерфейс приложения стал более интересным на вид и заметил, что во время "верстки" стал чаще пользоваться Preview.
Добавил горизонтальный (LazyRow) и вертикальный список (LazyColumn).
Прелесть в том, что не нужны больше адаптеры, но интересно будет рассмотреть пример с обновлением данных (кейс в котором часто используется DiffUtils, чтобы не обновлять список целиком).
Небольшой пример добавления списка элементов:
LazyRow {
items(hourItems) {
HoursItem(it)
Spacer(modifier = Modifier.size(8.dp))
}
}
items по сути заменяет нам цикл по элементам - можно для каждой модели быстро добавить макет элемента списка.Вообще, многие вещи в Compose настолько интуитивны, что описывать их особо нет смысла. Как-то все само собой получается, даже без документации, которая все еще скудновата. Но это больше актуально для каких-то базовых вещей.
В корневом элементе теперь SwipeToRefresh, который вынесен почему-то в отдельную библиотеку Accompanist и вам нужно дополнительно ее подключать.
С этим элементом есть проблема. Сам жест свайп ту рефреш сейчас работает только где пустое пространство, а не поверх всего макета как обычно и должно быть. Придется скорее всего немного подправить макет. Есть вариант использовать модификатор nestedscroll, но пока примеров с ним особо не нашлось. А если добавить в Column модификатор verticallscroll все крашится. Вообщем, пока все со свайп ту рефреш сыровато.
Кстати, в Appcompanist еще есть и Pager (аналог ViewPager), Insets, расширения работы с библиотеками загрузки изображений (Coil и Glide) и Permissions. Скорее всего список утилит в этой либе будет расширяться.
Разобрался с ConstraintLayout. Его гибкость по-прежнему с нами, но задавать все констрейнты в коде, конечно, не привычно:
ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
val (column, image, text1, text2) = createRefs()
Column(horizontalAlignment = Alignment.Start,
modifier = Modifier.constrainAs(column) {
start.linkTo(parent.start)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
}) {
Text(text = weatherData.dayOfMonth)
Text(text = weatherData.dayOfWeek)
}
Сперва нужно создать ссылки через createRefs(), которые мы потом присвоим нужным элементам, чтобы связывать их друг с другом.Text(
text = weatherData.minTemp.toString(),
style = Typography.caption,
modifier = Modifier.constrainAs(text2) {
end.linkTo(parent.end)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
}
)
Добавил базовую архитектуру: репозитории кидают мокированные данные, интерактор ими "управляет" и вьюмодель вытаскивает их из интерактора и на эти данные подписываются composable-функции. Управляет зависимостями Hilt. С ним чуть пришлось повозиться (баг с Gradle из поста выше). В итоге обошлось без передачи экземпляра вьюмодели в функцию, хотя сами вьюмодели можно получать и в скоупе composable-функции.
А в следующий раз поговорим про states (состояния), анимации и сделаем наш экран еще живее.
Репозиторий: https://github.com/Djangist/ComposeArchSample
Пост 16. Передача данных между фрагментом и диалогом через Navigation Component.
Недавно вспомнил о вариантах передачи данных между фрагментами, например, когда вызываемый фрагмент / диалог (DialogFragment) возвращает данные. Немного взгрустнув от того, что у нас в текущем проекте все еще старая версия библиотеки фрагментов и мы не можем использовать новый способ, вспомнил про еще одно решение с Navigation Component. Этот кейс тоже не для нашего старого проекта, в котором пришлось использовать setTargetFragment, но возможно, кому-то будет полезен.
1. Подписываемся на получение данных из вызываемого диалога / фрагмента:
2. Когда придет время - вызываем наш диалог или фрагмент обычным способом:
На этом все. Не нужны ни onActivityResult, ни разные костыли.
А про другие варианты передачи данных между фрагментами поговорим в следующих постах.
Недавно вспомнил о вариантах передачи данных между фрагментами, например, когда вызываемый фрагмент / диалог (DialogFragment) возвращает данные. Немного взгрустнув от того, что у нас в текущем проекте все еще старая версия библиотеки фрагментов и мы не можем использовать новый способ, вспомнил про еще одно решение с Navigation Component. Этот кейс тоже не для нашего старого проекта, в котором пришлось использовать setTargetFragment, но возможно, кому-то будет полезен.
1. Подписываемся на получение данных из вызываемого диалога / фрагмента:
private fun observeLocationDialogResult() {
findNavController().currentBackStackEntry
?.savedStateHandle
?.getLiveData<SetupLocationDialogFragment.Button>(KEY_BUTTON)
?.observe(viewLifecycleOwner, {
when (it) {
SetupLocationDialogFragment.Button.AUTO -> {
requestPermissions()
}
...
}
})
}
Здесь мы обращаемся к новому модулю SavedStateHandle (его, кстати, можно использовать и во ViewModel) и подписываемся на лайвдату, которую он любезно нам предоставил. Когда из диалога прилетит нужное нам значение (в нашем случае, когда пользователь нажмет на кнопку) - мы обработаем его нужным образом.2. Когда придет время - вызываем наш диалог или фрагмент обычным способом:
findNavController().navigate(MainFragmentDirections.actionMainFragmentToSetupLocationDialogFragment())
3. Обрабатываем нажатие на кнопку диалога или какое-либо другое событие в самом диалоге:binding.useAutoLocation.setOnClickListener {
findNavController().previousBackStackEntry?.savedStateHandle?.set(
KEY_BUTTON,
Button.AUTO
)
dismiss()
}
Здесь мы устанавливаем значение в поле, которое будем ловить в пункте 1. На этом все. Не нужны ни onActivityResult, ни разные костыли.
А про другие варианты передачи данных между фрагментами поговорим в следующих постах.
Столкнулся в пятницу с любопытным багом.
Если у вас в проекте используется библиотека Gson, то баг может быть актуальным и для вас.
Немного предистории. У нас есть Jenkins, который собирает билды тестеровщикам.
И вот в чем прикол: на моей локальной dev-сборке нужный запрос прекрасно работал, а у тестера нет.
Начали разбираться.
Сборка в дженкинсе обфусцируется и это была единственная разница между билдами.
И вот тут самое интересное.
Body для POST-запроса выглядит так:
А вот так выглядит наш класс для этого же Body:
Вроде бы все логично. Аннотации @SerializedName нет, ибо поле класса совпадает с названием поля тела POST-запроса.
Но штука вот в чем.
Если аннотации нет, поле обфусцируется в произвольное имя и запрос становится не корректным.
Поэтому лучше всегда дописывать к полям аннотацию @SerializedName:
Такие дела.
Если у вас в проекте используется библиотека Gson, то баг может быть актуальным и для вас.
Немного предистории. У нас есть Jenkins, который собирает билды тестеровщикам.
И вот в чем прикол: на моей локальной dev-сборке нужный запрос прекрасно работал, а у тестера нет.
Начали разбираться.
Сборка в дженкинсе обфусцируется и это была единственная разница между билдами.
И вот тут самое интересное.
Body для POST-запроса выглядит так:
{ query: "address string" }
А вот так выглядит наш класс для этого же Body:
data class SuggestionBody(
val query: String
)
Вроде бы все логично. Аннотации @SerializedName нет, ибо поле класса совпадает с названием поля тела POST-запроса.
Но штука вот в чем.
Если аннотации нет, поле обфусцируется в произвольное имя и запрос становится не корректным.
Поэтому лучше всегда дописывать к полям аннотацию @SerializedName:
data class SuggestionBody(
@SerializedName("query")
val query: String
)
Такие дела.
Пост 17. Передача данных между фрагментом и диалогом через set/getTargetFragment.
Продолжаем вспоминать какие существуют способы передачи данных между фрагментом и диалогом.
В этот раз поговорим о довольно часто используемом способе, но думаю не все о нем знают даже сейчас, в 2021 году. Особенно, если вы только начинаете изучать Android.
У фрагмента есть методы setTargetFragment и getTargetFragment (в случае использования Kotlin просто targetFragment). И если проект не самый новый - это довольно часто используемый вариант. Однако, этот способ стоит заменить на более новые варианты.
Пример использования.
Первым делом перед показом диалога мы добавляем вызов:
Константа REQUEST_CODE_TRY_AGAIN понадобится нам, чтобы отловить результат работы именно нашего диалога, который прилетит в onActivityResult.
Далее, во фрагменте, где запускается показ диалога и где нужно получить результат его работы мы переопределяем onActivityResult:
Далее, в самом диаложке (например, при клике на кнопку) мы дергаем targetFragment?.onActivityResult():
В последнем параметре обычно передается Intent с данными, но в нашем случае это не нужно.
Немного выглядит это все кривовато, не находите?
Тем не менее прекрасно работает. Но null-safety вызов onActivityResult здесь не просто так. Есть кейсы, когда targetFragment действительно null и тогда результата работы диалога мы не получим. У нас на проде такое встречалось, но очень редко и кейсы довольно специфичные.
Но стоит помнить, что данный способ передачи данных уже не рекомендован к использованию и метод getTargetFragment Deprecated in API level 28 (Android 9).
А какой подход нам дали взамен поговорим в следующий раз.
Продолжаем вспоминать какие существуют способы передачи данных между фрагментом и диалогом.
В этот раз поговорим о довольно часто используемом способе, но думаю не все о нем знают даже сейчас, в 2021 году. Особенно, если вы только начинаете изучать Android.
У фрагмента есть методы setTargetFragment и getTargetFragment (в случае использования Kotlin просто targetFragment). И если проект не самый новый - это довольно часто используемый вариант. Однако, этот способ стоит заменить на более новые варианты.
Пример использования.
Первым делом перед показом диалога мы добавляем вызов:
dialog.setTargetFragment(this, REQUEST_CODE_TRY_AGAIN)
Константа REQUEST_CODE_TRY_AGAIN понадобится нам, чтобы отловить результат работы именно нашего диалога, который прилетит в onActivityResult.
Далее, во фрагменте, где запускается показ диалога и где нужно получить результат его работы мы переопределяем onActivityResult:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_TRY_AGAIN && resultCode == Activity.RESULT_OK) {
presenter.tryAgain()
}
}
Далее, в самом диаложке (например, при клике на кнопку) мы дергаем targetFragment?.onActivityResult():
binding.tryAgain.setOnClickListener {
targetFragment?.onActivityResult(REQUEST_CODE_TRY_AGAIN, Activity.RESULT_OK, null)
dismiss()
}
В последнем параметре обычно передается Intent с данными, но в нашем случае это не нужно.
Немного выглядит это все кривовато, не находите?
Тем не менее прекрасно работает. Но null-safety вызов onActivityResult здесь не просто так. Есть кейсы, когда targetFragment действительно null и тогда результата работы диалога мы не получим. У нас на проде такое встречалось, но очень редко и кейсы довольно специфичные.
Но стоит помнить, что данный способ передачи данных уже не рекомендован к использованию и метод getTargetFragment Deprecated in API level 28 (Android 9).
А какой подход нам дали взамен поговорим в следующий раз.
Пост 18. Диалог с закруглением и произвольными отступами по краям.
Не так давно возникла задача сделать диалог с закругленными краями и прикрепленный к низу экрана с нужными отступами. Убедить дизайнера, что это не самая лучшая идея, увы, не вышло.
Почему не самая лучшая идея?
Во-первых, слишком «айосно», во-вторых, и это наиболее важный для меня пункт - для диалогов такого плана есть ботомшиты. Все таки у каждой платформы есть свои гайдлайны, которых стоит придерживаться. Да и пользовательский опыт платформы очень важен. А пользователи Android привыкли чаще всего видеть диалог по центру экрана.
Ну да ладно, зато задачка любопытная.
Прежде всего нам нужно создать нужный shape с закругленными краями:
Здесь стоит только обратить внимание на отступы снизу. Важный нюанс, иначе кнопка будет прилеплена к краю макета.
А теперь мы создадим ресурс, который и будет фоном диалога с отступами по краям:
Наш созданный shape здесь в качестве drawable и нужные отступы.
И теперь, чтобы все это заработало, нам нужно добавить несколько строк в коде самого диалога:
Понятное дело, без объекта WIndow не обошлось.
1. Ставим прозрачный фон, иначе будет видна не нужная белая часть диалога.
2. Указываем выравнивание диалога к нижнему краю (Gravity.Bottom).
3. И бонусом растягиваем окно диалога по ширине, иначе он будет выглядеть по ширине контента.
Получаем «красоту» как на скриншоте.
На всякий случай запушил пример в репу если кому-то понадобиться форкнуть себе: https://github.com/Djangist/RoundedDialogDemo
Не так давно возникла задача сделать диалог с закругленными краями и прикрепленный к низу экрана с нужными отступами. Убедить дизайнера, что это не самая лучшая идея, увы, не вышло.
Почему не самая лучшая идея?
Во-первых, слишком «айосно», во-вторых, и это наиболее важный для меня пункт - для диалогов такого плана есть ботомшиты. Все таки у каждой платформы есть свои гайдлайны, которых стоит придерживаться. Да и пользовательский опыт платформы очень важен. А пользователи Android привыкли чаще всего видеть диалог по центру экрана.
Ну да ладно, зато задачка любопытная.
Прежде всего нам нужно создать нужный shape с закругленными краями:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="https://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/purple_700" />
<corners android:radius="20dp" />
<padding android:bottom="16dp" />
</shape>
Здесь стоит только обратить внимание на отступы снизу. Важный нюанс, иначе кнопка будет прилеплена к краю макета.
А теперь мы создадим ресурс, который и будет фоном диалога с отступами по краям:
<?xml version="1.0" encoding="utf-8"?>
<inset
xmlns:android="https://schemas.android.com/apk/res/android"
android:drawable="@drawable/rounded_corners_shape"
android:insetRight="16dp"
android:insetLeft="16dp"
android:insetBottom="54dp">
</inset>
Наш созданный shape здесь в качестве drawable и нужные отступы.
И теперь, чтобы все это заработало, нам нужно добавить несколько строк в коде самого диалога:
with(requireDialog()) {
window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
window?.setGravity(Gravity.BOTTOM)
}
Понятное дело, без объекта WIndow не обошлось.
1. Ставим прозрачный фон, иначе будет видна не нужная белая часть диалога.
2. Указываем выравнивание диалога к нижнему краю (Gravity.Bottom).
3. И бонусом растягиваем окно диалога по ширине, иначе он будет выглядеть по ширине контента.
Получаем «красоту» как на скриншоте.
На всякий случай запушил пример в репу если кому-то понадобиться форкнуть себе: https://github.com/Djangist/RoundedDialogDemo
Когда проект растет есть два пути (два стула?) по которому обычно идут команды.
1. Сделать рефакторинг пакетов и вынести функционал по фичам, чтобы проще было искать и поддерживать нужный код. Разбиение по Clean Architecture тогда делается обычно внутри каждого пакета с фичей.
2. Вынести фичи в отдельные модули.
По первому варианту все понятно. Основная проблема - это сложность поиска нужного кода и более медленная сборка проекта.
А вот у второго варианта много и преимуществ и недостатков.
Хорошая статейка, которая освещает эти плюсы и минусы вот: https://proandroiddev.com/the-abc-of-modularization-for-android-in-2021-e7b3fbe29fca
Но на практике все не так радужно и лучше выбрать один из подходов до того как проект станет очень большим.
В нашем текущем проекте тоже используется многомодульность, но штука в том, что огромный кусок пакетов с фичами не вынесен в модули и валяется в так называемом базовом модуле. Как итог: теряется главное преимущество - скорость сборки проекта. Да и командная работа тоже не выигрывает от этой ситуации, ведь сразу много разрабов может что-то править в этом базовом модуле.
Спросите почему до сих пор не вынесли?
Не знаю, но справедливости ради кусок там очень большой и сейчас проект с нуля собирается 6-7 минут на iMac с 64 ГБ ОЗУ.
Если закладываться на многомодульность сразу, то такой ситуации, понятное дело, не возникнет, но на старте придется потратить времени побольше.
1. Сделать рефакторинг пакетов и вынести функционал по фичам, чтобы проще было искать и поддерживать нужный код. Разбиение по Clean Architecture тогда делается обычно внутри каждого пакета с фичей.
2. Вынести фичи в отдельные модули.
По первому варианту все понятно. Основная проблема - это сложность поиска нужного кода и более медленная сборка проекта.
А вот у второго варианта много и преимуществ и недостатков.
Хорошая статейка, которая освещает эти плюсы и минусы вот: https://proandroiddev.com/the-abc-of-modularization-for-android-in-2021-e7b3fbe29fca
Но на практике все не так радужно и лучше выбрать один из подходов до того как проект станет очень большим.
В нашем текущем проекте тоже используется многомодульность, но штука в том, что огромный кусок пакетов с фичами не вынесен в модули и валяется в так называемом базовом модуле. Как итог: теряется главное преимущество - скорость сборки проекта. Да и командная работа тоже не выигрывает от этой ситуации, ведь сразу много разрабов может что-то править в этом базовом модуле.
Спросите почему до сих пор не вынесли?
Не знаю, но справедливости ради кусок там очень большой и сейчас проект с нуля собирается 6-7 минут на iMac с 64 ГБ ОЗУ.
Если закладываться на многомодульность сразу, то такой ситуации, понятное дело, не возникнет, но на старте придется потратить времени побольше.
Medium
The ABC of Modularization for Android in 2021
Modularization is not a recent topic at all. This concept have been around us for years! but probably there are some folks around there…
Пост 19. Fragment Result API.
Продолжаем собирать разные способы передачи данных между фрагментами.
Теперь рассмотрим самый новый, судя по доке - рекомендуемый способ, с помощью Fragment Result API.
Fragment Result API появился с версии Fragment 1.3.0-alpha04.
Все предельно просто.
1. Во фрагменте, где нужно получить данные ставим обработчик:
KEY - ключ по которому будем ловить результат. Он должен совпадать с тем, что вернет диалог.
2. В диалоге по нажатию на кнопку возвращаем нужный результат:
На этом все.
Запушил изменения, если кто-то захочет посмотреть на живом примере: https://github.com/Djangist/RoundedDialogDemo
Продолжаем собирать разные способы передачи данных между фрагментами.
Теперь рассмотрим самый новый, судя по доке - рекомендуемый способ, с помощью Fragment Result API.
Fragment Result API появился с версии Fragment 1.3.0-alpha04.
Все предельно просто.
1. Во фрагменте, где нужно получить данные ставим обработчик:
setFragmentResultListener(KEY) { key, bundle ->
if (bundle.containsKey(key)) {
Toast.makeText(requireContext(), bundle[key].toString(), Toast.LENGTH_LONG).show()
}
}
setFragmentResultListener - это extension для parentFragmentManager.setFragmentResultListener(). Напомню, что extensions для фрагмента доступны в зависимосте: androidx.fragment:fragment-ktx.KEY - ключ по которому будем ловить результат. Он должен совпадать с тем, что вернет диалог.
2. В диалоге по нажатию на кнопку возвращаем нужный результат:
setFragmentResult(KEY, bundleOf(KEY to "Pff"))
Если в bundleOf передать другое значение, отличное от KEY, Toast не отобразится. На этом все.
Запушил изменения, если кто-то захочет посмотреть на живом примере: https://github.com/Djangist/RoundedDialogDemo
GitHub
GitHub - Djangist/RoundedDialogDemo: Custom rounded dialog with insets
Custom rounded dialog with insets. Contribute to Djangist/RoundedDialogDemo development by creating an account on GitHub.
Пятница.
Давайте устроим новую рубрику #вопросыответы дабы немного оживить наш канал.
Пишите вопросы под этим постом - потрындим о всяком разном айтишном, андроидном и разработческом.
Давайте устроим новую рубрику #вопросыответы дабы немного оживить наш канал.
Пишите вопросы под этим постом - потрындим о всяком разном айтишном, андроидном и разработческом.
Что новенького в рассылках #5
Ура, уже вышла RC2 для Compose, а значит релиз уже совсем скоро.
Уже начали что-то пробовать и изучать?
В связи с этим многие начали смотреть в сторону MVI-архитектуры presеentation-слоя, ибо Compose должен прекрасно на нее "ложиться".
Признаюсь, мне это особенно интересно. Хочется выработать новую архитектуру на ближайшие годы, дабы все новые приложения разрабатывать на ней.
В рассылках как раз множество статей на эту тему.
Например, вот:
https://medium.com/google-developer-experts/jetpack-compose-missing-piece-to-the-mvi-puzzle-44c0e60b571
Или вот сравнение LiveData vs SharedFlow в MVVM и MVI:
https://proandroiddev.com/livedata-vs-sharedflow-and-stateflow-in-mvvm-and-mvi-architecture-57aad108816d
Советы как улучшить свою продуктивность в Android Studio и немного продуктивность самой Studio:
https://proandroiddev.com/android-studio-tips-for-faster-development-cb9a17c123f3
Наличие качественных скриншотов у приложения в Google Play довольно важная для скачивания и просмотров вещь. Автор рассказывает какие средства и сервисы в этом могут помочь. Особенно актуально если вы все делаете сами и не хочется долго искать дизайнера:
https://proandroiddev.com/how-i-made-beautiful-screenshots-for-google-play-developer-experience-61ce108fa6b4
На неделе прошла небольшая онлайн-конфа Google на тему Gamedev на которой анонсировали Android Game Development Kit:
https://android-developers.googleblog.com/2021/07/introducing-android-game-development-kit.html
На десерт.
Pacman на Compose: https://github.com/danielmbutler/Pacman_Compose
Если пропустил что-то интересное - пишите в комментарии. Обсудим.
Ура, уже вышла RC2 для Compose, а значит релиз уже совсем скоро.
Уже начали что-то пробовать и изучать?
В связи с этим многие начали смотреть в сторону MVI-архитектуры presеentation-слоя, ибо Compose должен прекрасно на нее "ложиться".
Признаюсь, мне это особенно интересно. Хочется выработать новую архитектуру на ближайшие годы, дабы все новые приложения разрабатывать на ней.
В рассылках как раз множество статей на эту тему.
Например, вот:
https://medium.com/google-developer-experts/jetpack-compose-missing-piece-to-the-mvi-puzzle-44c0e60b571
Или вот сравнение LiveData vs SharedFlow в MVVM и MVI:
https://proandroiddev.com/livedata-vs-sharedflow-and-stateflow-in-mvvm-and-mvi-architecture-57aad108816d
Советы как улучшить свою продуктивность в Android Studio и немного продуктивность самой Studio:
https://proandroiddev.com/android-studio-tips-for-faster-development-cb9a17c123f3
Наличие качественных скриншотов у приложения в Google Play довольно важная для скачивания и просмотров вещь. Автор рассказывает какие средства и сервисы в этом могут помочь. Особенно актуально если вы все делаете сами и не хочется долго искать дизайнера:
https://proandroiddev.com/how-i-made-beautiful-screenshots-for-google-play-developer-experience-61ce108fa6b4
На неделе прошла небольшая онлайн-конфа Google на тему Gamedev на которой анонсировали Android Game Development Kit:
https://android-developers.googleblog.com/2021/07/introducing-android-game-development-kit.html
На десерт.
Pacman на Compose: https://github.com/danielmbutler/Pacman_Compose
Если пропустил что-то интересное - пишите в комментарии. Обсудим.
Medium
Jetpack Compose: Missing piece to the MVI puzzle?
When I first started exploring Jetpack compose, I saw many examples of the screen state modeled as a combination of mutable properties :
Эксперименты с Compose. Часть 3.
Продолжаем вникать в тонкости Compose.
Темы становятся сложнее и местами ощущаешь, что все непривычно после классического подхода.
Решил добавить в наш примерчик сплеш скрин с переходом на главный экран.
И знаете, что интересно?
После этого подумал: а зачем нам теперь нужны фрагменты?
Но давайте поясню на примере.
Теперь в активность мы добавляем NavHost, который будет выступать роутером для наших двух экранов. Да, да, у Compose есть привязка к Navigation Component.
Все относительно просто. Ставим сплеш скрин как стартовый экран (startDestination). А далее уже в зависимости от обычного вызова navController.navigate(«route») произойдет переход на нужную Composable функцию, которая является для макета стартовой.
А вот так выглядит макет нашего сплеш-скрина:
Здесь основная магия происходит в переменной state. В качестве начального состояния мы ставим UIState.Showing (будет чуть нагляднее в коде вьюмодели) и после того как наш стейт (состояние) меняется на NavigateTo мы вызываем navigateTo("main") для перехода на главный экран.
Чтобы понять лучше как это все работает, стоит вникнуть в так называемую концепцию recomposition, которая является основной для Compose. Суть в том, что определенные composable-функции будут перерисовываться в зависимости от изменения состояния. Чуть подробнее мы поговорим об этом отдельно (нужно еще самому вникнуть как следует).
И последний пазл нашего примера - код SplashViewModel:
Ждем три секунды и меняем стейт на NavigateTo, что позволит нашей composable-функции отрисоваться заново, с новым состоянием, которое мы ловим и переходим на главный экран.
И самое интересное - у нас нет никаких фрагментов.
Как вам? Звучит все это сложно?
Местами, пожалуй, да.
Вникнуть будет проще тем, кто уже работал с разными либами на подобном реактивном подходе (redux, Flutter и прочие) с MVI в основе.
Код примера здесь: https://github.com/Djangist/ComposeArchSample
Далее, мы отдельно поговорим о рекомпозиции, remember и mutableStateOf.
Продолжаем вникать в тонкости Compose.
Темы становятся сложнее и местами ощущаешь, что все непривычно после классического подхода.
Решил добавить в наш примерчик сплеш скрин с переходом на главный экран.
И знаете, что интересно?
После этого подумал: а зачем нам теперь нужны фрагменты?
Но давайте поясню на примере.
Теперь в активность мы добавляем NavHost, который будет выступать роутером для наших двух экранов. Да, да, у Compose есть привязка к Navigation Component.
setContent {
ComposeAppArchitectureTheme {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "splash") {
composable("splash") {
SplashScreen(splashViewModel, navController)
}
composable("main") {
MainScreen(viewModel)
}
}
}
}
Все относительно просто. Ставим сплеш скрин как стартовый экран (startDestination). А далее уже в зависимости от обычного вызова navController.navigate(«route») произойдет переход на нужную Composable функцию, которая является для макета стартовой.
А вот так выглядит макет нашего сплеш-скрина:
@Composable
fun SplashScreen(viewModel: SplashViewModel, navController: NavController) {
val state = remember { viewModel.state }
if( state.value is UIState.NavigateTo ){
Log.d(TAG,"navigate to main")
navController.popBackStack()
navController.navigate("main")
}
Log.d(TAG,"splash recompose ${state.value}")
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.background(SplashColor),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
painter = painterResource(id = R.drawable.splash),
contentDescription = null,
alignment = Alignment.Center
)
}
}
Здесь основная магия происходит в переменной state. В качестве начального состояния мы ставим UIState.Showing (будет чуть нагляднее в коде вьюмодели) и после того как наш стейт (состояние) меняется на NavigateTo мы вызываем navigateTo("main") для перехода на главный экран.
Чтобы понять лучше как это все работает, стоит вникнуть в так называемую концепцию recomposition, которая является основной для Compose. Суть в том, что определенные composable-функции будут перерисовываться в зависимости от изменения состояния. Чуть подробнее мы поговорим об этом отдельно (нужно еще самому вникнуть как следует).
И последний пазл нашего примера - код SplashViewModel:
@HiltViewModel
class SplashViewModel @Inject constructor() : ViewModel() {
private val _state = mutableStateOf<UIState>(UIState.Showing)
val state
get() = _state
init {
viewModelScope.launch {
delay(3000)
_state.value = UIState.NavigateTo("main")
}
}
}
Ждем три секунды и меняем стейт на NavigateTo, что позволит нашей composable-функции отрисоваться заново, с новым состоянием, которое мы ловим и переходим на главный экран.
И самое интересное - у нас нет никаких фрагментов.
Как вам? Звучит все это сложно?
Местами, пожалуй, да.
Вникнуть будет проще тем, кто уже работал с разными либами на подобном реактивном подходе (redux, Flutter и прочие) с MVI в основе.
Код примера здесь: https://github.com/Djangist/ComposeArchSample
Далее, мы отдельно поговорим о рекомпозиции, remember и mutableStateOf.
Скоро мы все будем не нужны...
Отчасти шутка, конечно, но насколько долго она будет именно шуткой - вопрос интересный.
Уже видели?
https://copilot.github.com
Отчасти шутка, конечно, но насколько долго она будет именно шуткой - вопрос интересный.
Уже видели?
https://copilot.github.com
GitHub
GitHub Copilot
AI that builds with you
Немного изменений в демке. Времени пока не так много на нее, увы.
На полноценную часть про Compose не тянет, но пару интересных изменений о которых хочется рассказать все же есть.
1. Звучит как шутка, но наконец-то победил SwipeRefresh и сам свайп теперь работает из любой части макета, без краша. Пришлось добавить во все нужные функции ниже корневой модификатор verticalScroll(rememberScrollState()). Если добавлять в корневой Column - краш!
2. Теперь экземпляр вьюмодели получаем прямиком из Composable-функции через hiltViewModel()
Эта функция доступна в отдельной либе androidx.navigation:navigation-compose:2.4.0-alpha04
Кстати, на более свежую версию пока обновляться не советую. Вся навигация ломается на хрен! Начинается бесконечный recompose сплеш-скрина.
3. Убрал не нужные элементы, вроде вложенных Column. Стало немного чище.
Из интересного еще вот что.
hiltViewModel() стоит использовать если у вас есть навигация через NavComponent и инжекты через Hilt. В MainScreen эта функция как раз и выручает, а если будем использовать обычный метод viewModel() - угадайте что? Правильно! Краш.
Хотя в SplashScreen прекрасно работает и viewModel(), но на всякий случай решил использовать и там hiltViewModel(), для консистентности.
Код, как обычно, здесь: https://github.com/Djangist/ComposeArchSample
На полноценную часть про Compose не тянет, но пару интересных изменений о которых хочется рассказать все же есть.
1. Звучит как шутка, но наконец-то победил SwipeRefresh и сам свайп теперь работает из любой части макета, без краша. Пришлось добавить во все нужные функции ниже корневой модификатор verticalScroll(rememberScrollState()). Если добавлять в корневой Column - краш!
2. Теперь экземпляр вьюмодели получаем прямиком из Composable-функции через hiltViewModel()
Эта функция доступна в отдельной либе androidx.navigation:navigation-compose:2.4.0-alpha04
Кстати, на более свежую версию пока обновляться не советую. Вся навигация ломается на хрен! Начинается бесконечный recompose сплеш-скрина.
3. Убрал не нужные элементы, вроде вложенных Column. Стало немного чище.
Из интересного еще вот что.
hiltViewModel() стоит использовать если у вас есть навигация через NavComponent и инжекты через Hilt. В MainScreen эта функция как раз и выручает, а если будем использовать обычный метод viewModel() - угадайте что? Правильно! Краш.
Хотя в SplashScreen прекрасно работает и viewModel(), но на всякий случай решил использовать и там hiltViewModel(), для консистентности.
Код, как обычно, здесь: https://github.com/Djangist/ComposeArchSample
GitHub
GitHub - Djangist/ComposeArchSample
Contribute to Djangist/ComposeArchSample development by creating an account on GitHub.
Проблема с минификацией в com.android.tools.build:gradle:4.2.2
Не знаете как провести рабочий день? Скучно пилить новые бизнес-фичи?
Можно, например, попытаться понять в чем заключается проблема
java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard
с API-методом в Retrofit, который возвращает обычный Single<ResponseDto>.
Оказывается, в версии 4.2.2 плагина com.android.tools.build:gradle намудрили что-то с минификацией и он выкашивает dto-объекты ответа, которые не используются.
Суть проблемы: https://github.com/square/retrofit/issues/3588
Распространите коллегам, чтобы были в курсе, а то минус день-другой обеспечены.
Не знаете как провести рабочий день? Скучно пилить новые бизнес-фичи?
Можно, например, попытаться понять в чем заключается проблема
java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard
с API-методом в Retrofit, который возвращает обычный Single<ResponseDto>.
Оказывается, в версии 4.2.2 плагина com.android.tools.build:gradle намудрили что-то с минификацией и он выкашивает dto-объекты ответа, которые не используются.
Суть проблемы: https://github.com/square/retrofit/issues/3588
Распространите коллегам, чтобы были в курсе, а то минус день-другой обеспечены.
GitHub
Method return type must not include a type variable or wildcard · Issue #3588 · square/retrofit
Using com.squareup.retrofit2:retrofit:2.9.0 After upgrading gradle plugin from com.android.tools.build:gradle:4.1.3 to com.android.tools.build:gradle:4.2.2 I'm getting following error when ...
Вышел Compose 1.0!
C одной стороны прекрасная новость и можно активнее учить основы, пробовать внедрять какие-то кусочки UI (без фанатизма), а с другой привязки разных библиотек к Compose все еще в альфа, бета-версиях, что не очень.
Кстати, обновил демо проект на версию 1.0.
Немного радости от Compose Team:
https://www.youtube.com/watch?v=kLA1QwDjioc
C одной стороны прекрасная новость и можно активнее учить основы, пробовать внедрять какие-то кусочки UI (без фанатизма), а с другой привязки разных библиотек к Compose все еще в альфа, бета-версиях, что не очень.
Кстати, обновил демо проект на версию 1.0.
Немного радости от Compose Team:
https://www.youtube.com/watch?v=kLA1QwDjioc
YouTube
Announcing Jetpack Compose 1.0
Today, we're launching version 1.0 of Jetpack Compose, Android's modern, native UI toolkit to help you build better apps faster. It's stable, and ready for you to adopt in production.
Our team has been developing Compose in the open with feedback and participation…
Our team has been developing Compose in the open with feedback and participation…
Любопытная библиотечка для дебаггинга на самом девайсе.
Показывает логи HTTP-запросов, собирает краши приложения, а еще можно смотреть shared preferences и подправлять на нужное значение для отладки.
Вообщем, может пригодится в повседневной работе.
Ссылка на либу: https://github.com/mocklets/pluto
Показывает логи HTTP-запросов, собирает краши приложения, а еще можно смотреть shared preferences и подправлять на нужное значение для отладки.
Вообщем, может пригодится в повседневной работе.
Ссылка на либу: https://github.com/mocklets/pluto
GitHub
GitHub - androidPluto/pluto: Android Pluto is a on-device debugging framework for Android applications, which helps intercept Network…
Android Pluto is a on-device debugging framework for Android applications, which helps intercept Network calls, capture Crashes & ANRs, manipulate application data on-the-go, and much more....
Кстати, а вы обновили студию до Arctic Fox?
Она уже где-то неделю в stable. Я обновился и работаю в Arctic Fox и над старым (основным рабочим) проектом и над новыми обучающими с Compose на борту.
Полет пока нормальный, но есть нюансы:
1. Аккуратнее с R8 в релизных билдах. Теперь он более рьяно выпиливает неиспользуемые классы. Недавний пример есть на канале.
2. Выпилена инструкция compile и ее производные. Теперь только implementation.
3. Теперь AGP 7.0 (Android Gradle Plugin) требует Gradle 7.0 и Java 11.
4. Новый Layout Inspector теперь позволяет строить дерево composable-функций, смотреть как они отрисованы, просматривать параметры и т.д. - довольно удобно!
5. Preview для Compose. Его можно настроить довольно гибко, добавляя по нескольку превьюшек для одной функции, включать системный интерфейс, фоны и прочее. Здесь пока есть проблемы с отрисовкой (превью иногда не работает), но думаю допилят в ближайших обновлениях.
6. Теперь можно вытащить sqlite базу к себе на диск и выбрать при экспорте нужный формат. Не то чтобы вау-новость, но теперь можно быстрее вытаскивать БД и смотреть проблемы с таблицами, хотя и раньше можно было.
7. И самое любопытное для меня. В AGP 7.0 убрали build cache, при этом заявляют, что скорость сборки не просядет. Убрали задачу cleanBuildCache, свойства android.enableBuildCache, android.buildCacheDir.
Видосик: https://www.youtube.com/watch?v=-8tSZr7iMcw
Она уже где-то неделю в stable. Я обновился и работаю в Arctic Fox и над старым (основным рабочим) проектом и над новыми обучающими с Compose на борту.
Полет пока нормальный, но есть нюансы:
1. Аккуратнее с R8 в релизных билдах. Теперь он более рьяно выпиливает неиспользуемые классы. Недавний пример есть на канале.
2. Выпилена инструкция compile и ее производные. Теперь только implementation.
3. Теперь AGP 7.0 (Android Gradle Plugin) требует Gradle 7.0 и Java 11.
4. Новый Layout Inspector теперь позволяет строить дерево composable-функций, смотреть как они отрисованы, просматривать параметры и т.д. - довольно удобно!
5. Preview для Compose. Его можно настроить довольно гибко, добавляя по нескольку превьюшек для одной функции, включать системный интерфейс, фоны и прочее. Здесь пока есть проблемы с отрисовкой (превью иногда не работает), но думаю допилят в ближайших обновлениях.
6. Теперь можно вытащить sqlite базу к себе на диск и выбрать при экспорте нужный формат. Не то чтобы вау-новость, но теперь можно быстрее вытаскивать БД и смотреть проблемы с таблицами, хотя и раньше можно было.
7. И самое любопытное для меня. В AGP 7.0 убрали build cache, при этом заявляют, что скорость сборки не просядет. Убрали задачу cleanBuildCache, свойства android.enableBuildCache, android.buildCacheDir.
Видосик: https://www.youtube.com/watch?v=-8tSZr7iMcw
YouTube
What's new in Android Studio Arctic Fox
Get an overview of what tools and features are available in Android Studio Arctic Fox and the new updates and requirements to Android Gradle Plugin 7.0.
New features:
•Compose support (preview, interactive preview, layout inspector)
•Accessibility Scanner…
New features:
•Compose support (preview, interactive preview, layout inspector)
•Accessibility Scanner…
Небольшая quiz-задачка по Compose, которая прекрасно раскрывает суть recomposing (рекомпозиции) - одной из основных концепций нового декларативного UI.
Грубо говоря, суть ее в том, что элемент UI будет обновлен (отрисован повторно), если изменится его состояние, а если часть UI остается в прежнем состояние, то Compose просто возьмет уже закешированное (ранее уже отрисованное состояние). Таким образом меняется лишь часть UI, а не все дерево целиком, как обычно происходит в xml.
У нас есть такой код:
Грубо говоря, суть ее в том, что элемент UI будет обновлен (отрисован повторно), если изменится его состояние, а если часть UI остается в прежнем состояние, то Compose просто возьмет уже закешированное (ранее уже отрисованное состояние). Таким образом меняется лишь часть UI, а не все дерево целиком, как обычно происходит в xml.
У нас есть такой код:
@Composable
fun Foo() {
Log.d(TAG,"recompose Foo")
var text by remember { mutableStateOf("") }
Log.d(TAG,"recompose Foo text")
Button(onClick = { text = "$text\n$text" }) {
Log.d(TAG,"recompose Button's lambda")
Text(text)
}
}Какой текст выведется в лог если несколько раз нажать на кнопку?
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 и далее стейт (состояние) при каждом клике уже меняться не будет.