Старый Мобильщик
74 subscribers
34 photos
1 video
1 file
118 links
Разработка мобильных приложений, дедлайны и все, что вы любите в IT.

Будни. Сниппеты. Заметки.

Когда-то были AsyncTasks ... Android 2.3.3 и ни одной вакансии в городе-миллионнике

Обсудить что-либо: @activitynotfound
Download Telegram
Принцип проектирования по контракту

Бертран Мейер разработал принцип проектирования по контракту для языка Eiffel. Это простая, но эффективная методика, направленная на документирование (и согласование) прав и обязанностей программных модулей, чтобы обеспечить правильность программы. А что такое правильная программа? Это программа, которая выполняет только то, что от нее требуется, - ни больше и ни меньше. Документирование и верификация такого требования и составляет саму суть проектирования по контракту.

Каждая функция и метод в программной системе выполняет какое-то действие. Прежде чем начать это действие, функция может предполагать какое-то состояние окружающего мира, а по завершении она может сделать заявление о состоянии окружающего мира (перевод на русский, вероятно, не самый удачный, но суть отражает). Эти предположения и требования Мейер описывает следующим образом.
- Предусловия. Это требования подпрограммы, которые определяют, что должно быть истинным для ее вызова. Подпрограмма вообще не должна вызываться, если ее предусловия будут нарушены.
- Постусловия. Это состояние окружающего мира по завершении подпрограммы, т.е. То, что ею гарантируется. Наличие постусловия у подпрограммы подразумевает, что она непременно завершится, а следовательно, бесконечные циклы не допускаются.
- Инварианты класса. Класс гарантирует, что данное условие для вызывающего кода всегда истинно. В процессе внутренней обработки в подпрограмме инвариант может и не соблюдаться, но к моменту выхода из подпрограммы и передачи управления вызывающему коду инвариант должен быть непременно истинным.

Таким образом, контракт между подпрограммой и любым потенциально вызывающим кодом может быть составлен следующим образом.
- Если все предусловия подпрограммы удовлетворяются вызывающим кодом, то подпрограмма гарантирует истинность всех постусловий и инвариантов при своем завершении.

К чему это все?
Оказывается, в Kotlin 1.3 появился пакет kotlin.contracts и у нас есть возможность попробовать использовать этот принцип на практике, делая наши приложения более документированными и безопасными.
А вот примеры использования этого пакета: https://blog.kotlin-academy.com/understanding-kotlin-contracts-f255ded41ef2

Кстати, а вы слышали про подобный принцип проектирования ранее?
Небольшой дайджест изменений во Fragment API 1.4.0 (релиз от 17 Ноября)

- Появился метод FragmentContainerView.getFragment()
Раньше, чтобы получить фрагмент нужно было дергать метод:

val fragment = fragmentManager.findFragmentById()

и приводить к нужному типу, а теперь же фрагмент можно получить через метод контейнера:

val fragment = binding.container.getFragment<NavHostFragment>()


- Новый менеджер состояний (до 1.4.0 был экспериментальным)

- Поддержка множественных back stacks (также доступно в Navigation Component)
Подробнее здесь: https://medium.com/androiddevelopers/multiple-back-stacks-b714d974f134

- FragmentStrictMode - разные проверки потенциальных проблем при работе с фрагментами и возможность на них реагировать.
К примеру, можно отправить какие-то кастомные исключения/события в Firebase Crashlytics.
Задать настройки можно через метод setStrictModePolicy()
Подробнее:https://developer.android.com/guide/fragments/debugging#strictmode

- Новые проверки для Android Lint

- FragmentScenario теперь реализует Closeable и их можно использовать в Kotlin-овском use и try-with-resources.
Подробнее про FragmentScenario и тестирование фрагментов: https://developer.android.com/guide/fragments/test
Друзья, а вы пользуетесь итераторами?
Просто пост-напоминание, что иногда и они могут быть полезны.

Простая задачка: поменять значение одного из элементов списка, индекс которого мы не знаем.

Можно воспользоваться listIterator() таким вот образом:

val someList = mutableListOf(1, 20, 10, 55, 30, 22, 11, 0, 99) // или arrayListOf()
val iterate = someList.listIterator()
while (iterate.hasNext()) {
val oldValue = iterate.next()
if (oldValue == 20) {
iterate.set(oldValue + 20)
}
}

Но изменять значения итератор само собой позволит только у MutableList. У обычного List метода set нет, что логично.
Давайте поиграем в игру "Собеседование".
Представьте, что собеседуете человека.
На скрине код кандидата.

1. Что в нем не так? Сколько проблем сходу видите?
2. На какой уровень должен претендовать кандидат с таким кодом? (jun / middle / senior / lead )
3. Вопросы по каким темам вы бы у него спросили после ознакомления с таким кодом?
И так, продолжим с ответов на вопросы выше про код кандидата. Придется отвечать мне, раз уж вы такие молчаливые 🙂

Кандидат хотел устроиться к нам в банк, в довольно сложный стрим. Особо про его резюме не будем. Парень окончил курсы на Udemy, поработав годик-около двух.
Хотелось бы послушать про его опыт, а то там уже и джунов полидить успел, при том что в коде, который он прислал нам, активно участвовал еще один разраб. Однако, кандидат принял оффер другой кампании и к нам на собес не пришел.

Но давайте все таки про код.
1. Напрямую в репозиторий передается экземпляр Application. Во-первых, это мягко говоря не очень из-за возможных проблем с утечками, а во-вторых в репозитории не нужно передевать никакие зависимости от Android.
2. Так называемый mapper (CoinMapper) не передается в качестве зависимости в конструкторе и сам маппер написано неправильно.
3. И вообще, зависимость подставляется вручную, не через DI-контейнер. Для примера не прям уж критично, если человек может рассказать почему так и как работает DI, но даже использовав условный Hilt можно добавить себе "дополнительных очков" в коде.
4. Экземпляр БД тоже лучше передавать в зависимость в конструкторе.
5. Репозиторий не должен возвращать LiveData (опять же, пункт про Application). Репозиторий должен возвращать список моделей, а дальше уже на уровне Presentation решается как с этим быть дальше.
6. Небрежное форматирование. Писали быстро. Не критично, но такая небрежность настораживает.
7. Работу с WorkManager можно смело вынести в отдельную зависимость.

Кандидата стоит смело погонять по Clean Architecture, MVVM, MVP, leak memory, многомодульности (у нас очень много модулей), DI и само собой по основам Android.

Исходя из проблем выше, кандидата можно оценить как Джуниор-разработчика, может быть Джуниор+/начинающий Миддл если бы мы послушали про его опыт. Здесь важно сопоставить архитектуру и проблемы вашего проекта и опыт кандидата. Но судя по резюме, человек себя позиционирует выше Джуна, что немножко настораживает после увиденного кода.

Таких кандидатов бояться не стоит, но на сложный стрим если и брать, то в пару с ментором, который будет его вести, что в реальной жизни крайне редко встречается.
👍1
Наконец-то стало больше времени и теперь поговорим о KMM-проекте.
Напомню локацию: https://github.com/Djangist/ToDoApp

Что нового:
- Compose теперь работает и на Kotlin 1.6 (версия Compose Compiler должна быть 1.1.0-rc02)
- Появился набросок главного экрана со списком заметок. На обеих платформах. Для iOS используется SwiftUI, который появился, к слову, раньше чем Compose и очень похож по концепции. Самая сложная часть была именно здесь (ну, понятно почему). А вот на Compose главный экран накидать удается довольно быстро.
- Общий модуль shared корректно подключается в оба проектах. В этом модуле у нас будет вся бизнес-логика. Сейчас это модель Todo и репа TodoRepository. В дальнейшем здесь будет все по Clean: интеракторы и прочие общие вещи.
- Типы в Kotlin немного отличаются от типов в Swift. Пример наш репозиторий, который возвращает List<Todo>. В Swift это будет Array<Todo>. Благо в доке есть сравнительная табличка.
- Если вы решите писать оба проекта сами (а во многом на это и будет расчет у заказчиков) - учите iOS / Swift / SwiftUI. Пришлось потратить около часа! чтобы разобраться как динамически вывести список Todo. В этом нам помогла функция.

let todos = TodoRepositoryImpl().getAllTodos() // получаем список заметок (код в модуле shared)
...
VStack(spacing: 8) {
ForEach(todos, id: \.self){ todo in
VStack {
Text(todo.description_)
.font(.headline)
.padding(.top)
.padding(.leading)
.frame(maxWidth: .infinity,
alignment: .leading)
Text(todo.text)
.font(.subheadline)
.padding(.leading)
.frame(maxWidth: .infinity,
alignment: .leading)
}
}
Spacer()

И в этом отношении Flutter выглядит для простых проектов интереснее: один язык для всего, а выучить Dart зная условную Java не так сложно!
- XCode - это боль! Ну либо я к нему все еще не привык. Автодополнение работает через жопу, все медленно, не интуитивно. Редактировать код просто не комфортно. Но хочется похвалить работу Preview - оно проще и быстрее чем в AS для Compose. В теории, можно обойтись и без XCode, но там больше нужных вещей и конфиги редактировать иногда придется. Все еще мечтаю попробовать в январе AppCode.
- MacPro 16 c интелом на борту подлагивает при открытых проектах и эмуляторе. Работать можно, но уже не так комфортно как в одной лишь AS.
- Нужно привыкать к яблочной доке. Если вы ругаете доку от Гугла, посмотрите яблочную ) Мало того, что найти нужную инфу не так просто, так еще и инфы как-то не хватает и структурировано все не очень явно. В стиле яблока короче.
- Ключевая (ну или одна) из концепций в KMM - это expect / actual. Когда в общем модуле shared нужен какой-то платформенно-специфичный код вы объявляете class / fun как expect, а в конкретном пакете того же модуля потом объявленный класс или функцию реализуете (actual class / fun). Пример на скрине.
- Иконки. В Compose вы просто используете Icon(Icons.Filled.Add) выбирая нужную в AS, но в iOS все конечно же не так... Оказывается там системную иконку нужно выбрать по названию строки: Image(systemName: "plus.circle.fill")
Какой пипец. Но не спешите расстраиваться. Есть отдельное приложение для просмотра / поиска системных иконок - https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview/ Непривычно, что по названию строки нужно выбирается иконка, но хрен с ним - можно привыкнуть.

На этом из нового пока все.

С наступающим вас!
Пусть в 2022-ом в вашем коде будет как можно меньше багов и как можно больше оплаченных строк и интересных проектов!
Попробовал таки AppCode для iOS-проекта (который часть KMM-проекта).

Что понравилось?
- Автодополнение и подсветка параметров функций и методов работает лучше, чем в Xcode. В целом с кодом приятно работать (как и в любой IDEA-based среде)
- Проект без проблем запускается на iOS-симуляторе
- Все доступные симуляторы среда также подсвечивает (как обычно сделано в Android Studio)
- Проект без проблем импортируется и открывается.
- Можно редактировать и .plist-файлы и pod-файлы.

Чего явно не хватает?
- Превью для SwiftUI. Все таки фишка приятная и не хочется от нее отказываться.
- Редактирование конфига проекта. Для новичка в iOS это все таки удобно и тут Xcode снова выигрывает.

В целом впечатления от AppCode приятные и видится, что JetBrains скоро допилит и эти фичи тоже, но пока среда не выглядит, как полноценная замена XCode.
Отладку пока толком не смотрел, но из того, что видил в Xcode она пока еще более мощная (там и память и сеть и дебаггинг более плотный, но вот тут надо сравнивать).

Опыта в AppCode (да и в iOS), конечно, маловато и я еще повожусь с ней, пока действует Trial до конца Января, но поймал себя на мысли, что все таки привычка работать в бесплатной, классной среде сказывается (это я про Android Studio), поэтому для себя пока не понял стоит ли платить 200$ в год за AppCode .
Поддержать JetBrains?
Это, конечно да, но я сейчас о скорее о полноценной замене XCode, а то как-то много сред приходится держать открытыми!
👍1
Поигрался немного со Swift попутно просматривая курс для новичков:
https://www.youtube.com/watch?v=ZJcVUSfdVyo
Не смотря на то, что курс больше для новичков в программировании, нашел там для себя ответы на вопросы по синтаксису языка и остался доволен потраченными 5 часами.
Также закинул на Gist свой .playground, который набирал во время просмотра видео (кому-то может сэкономит время):
https://gist.github.com/Djangist/5d7e1cedf01ced57b0f399624fafd1fa

Playground - специальный режим XCode, позволяющий поиграться с языком, при чем довольно удобный: можно писать какие-то куски кода, быстро прогнать или например удобно решать какие-то алгоритмические задачки, пробовать фичи языка.
Как это выглядит показано на скриншоте.

Если кто надумает поучить Swift - рекомендую начать с этого курса + оф дока на сайте swift.org
Если будут нужны еще какие-то материалы по Swift - пишите в комменты.
Виджет поиска на Compose. Часть 1. Базовая реализация.

Мы в команде постепенно создаем компонентную базу на Compose, чтобы в недалеком будущем перейти полностью на новый UI. Переход будет плавным, поэтому будет время набить нужные шишки с Compose.

Взял себе задачку создать компонент поиска - как раз будет что рассказать вам!
В этой часте поговорим про базовую реализацию и начнем сразу с кода.

@ExperimentalUnitApi
@Composable
fun SearchView(
value: String = "",
hintText: String = "",
hintColor: Color = Color(0xFF797F90),
contentDescription: String = "search widget",
cornerRadius: Dp = 12.dp,
backgroundColor: Color = Color(0xFFF5F5F7),
textChangedCallback: (String) -> Unit = {}
) {
TextField(
modifier = Modifier.background(
color = backgroundColor,
),
value = value,
leadingIcon = {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(id = R.drawable.ic_search),
contentDescription = contentDescription,
)
},
singleLine = true,
onValueChange = { textChangedCallback(it) },
shape = RoundedCornerShape(cornerRadius),
colors = TextFieldDefaults.textFieldColors(
placeholderColor = hintColor,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent
),
placeholder = {
Text(
text = hintText,
style = TextStyle(fontSize = TextUnit(16.0f, TextUnitType.Sp))
)
}
)
}


- По большому счету весь компонент строится вокруг поля ввода - TextField. Посмотрим позже достаточно ли этого или придется вложить его в Row.
- Самое сложное на первом этапе - найти все нужные атрибуты базового виджета (проще всего через исходники) и спроектировать параметры нашего компонента.
- Все что нужно настроить извне выносим в параметры функции (например, цвета, размер шрифта и callback, который дергается во время ввода)
- Почти все параметры со значением по умолчанию для гибкости и простоты вызова.
- Placeholder может быть довольно сложным благодаря Composable-лямбде.
- Хорошая практика - передавать в кастомный компонент modifier, который настраивается извне. В этой часте пока это не реализовано, но будет добавлено чуть позже.

Пока ничего сложного, однако стоит еще добавить работу с состоянием компонента (кеш) и другие мелочи, чем позже и займемся.
Скрин как это выглядит в Preview.
👍2
DI в KMM. Первые пробы с Koin.

Koin был одним из первых DI-фреймворков, кто начал поддерживать KMM. Но стоит напомнить, что Koin - Service Locator, поэтому DI с ним довольно условный. В простом проекте иногда можно (будем себя так успокаивать) для быстрого старта. Запасайтесь кофем - букв много.

Чтобы добавить поддержку Koin в наш KMM-проект нужно:
1. Добавляем все зависимости Koin в модуль shared
2. Добавляем в пакет shared/commonMain код в файл Koin.kt:


fun initKoin() = startKoin {
printLogger(Level.ERROR)
modules(commonModule)
}

// called by iOS etc
fun initKoinIos() = startKoin {
printLogger(Level.ERROR)
modules(commonModule)
}

val commonModule = module {
single { TodoRepositoryImpl() }
}

@Suppress("UNCHECKED_CAST")
fun <T> Koin.getDependency(clazz: KClasslt;*>): T {
return get(clazz, null) { parametersOf(clazz.simpleName) } as T
}


commonModule - наш общий Koin-модуль с зависимостями для обеих платформ.
initKoin соответственно мы будем дергать из Android-приложения, а initKoinIos - ну вы поняли.

3. в iosMain мы должны добавить свою реализацию метода получения зависимостей, так как на входе у нас будет другой тип. Создаем в этом пакете Koin.kt с кодом:

fun lt;T> Koin.getDependency(objCClass: ObjCClass): T? = getOriginalKotlinClass(objCClass)?.let {
getDependency(it)
}


Теперь вызываем на каждый платформе нужную версию метода init и в целом из основного все.

В Android мы делаем дальше все как обычно, вызывая Koin-ский inject() и добавляя либы Koin-а к проекту. Получаем наш общий репозиторий как обычно:

@Composable
fun MainScreen() {
val repository: TodoRepositoryImpl by inject()
val todos = repository.getAllTodos()

Вы можете заметить, что мы получаем Impl, а не интерфейс и будете правы. Пока что с типами есть проблема. Если указывать в модуле именно интерфейс - краш на iOS, если поменять тип при инжекте в Android - краш на Android. Так что тут есть проблемка над которой стоило бы посидеть.

В iOS мы используем Koin немного иначе.
Создаем Koin.swift с небольшим хелпером:

private var _koin: Koin_coreKoin?

var koin: Koin_coreKoin {
return _koin!
}

func startKoin() {
_koin = KoinKt.doInitKoinIos().koin
}

extension Koin_coreKoin {
func get() -> TodoRepository {
koin.getDependency(objCClass: TodoRepositoryImpl.self) as! TodoRepository
}
}

startKoin мы дергаем в App (как впрочем и на Android)
Обратите внимание, что метод initKoiniOS на iOS стал называться doInitKoinIOS(). Также мы добавляем экстеншен для типа Koin_coreKoin для небольшого сахарка. С помощью этого метода и будем получать нужные зависимости в ContentView:

var repo: TodoRepository
let todos: Arraylt;Todo>
init() {
repo = koin.get()
todos = repo.getAllTodos()
}

С Koin-ом пока наверное закончим.
Примерно так чаще всего реализуется DI в KMM.

Дальше есть мысль попробовать настоящий DI: https://github.com/sergeshustoff/dikt
Хочется получать не краши, а ошибки компиляции в случае проблем с зависимостями. В KMM-проектах это будет экономить кучу времени при отладке!
👍3
Прикольный репозиторий с типичными вопросами-ответами на Compose:
https://github.com/vinaygaba/Learn-Jetpack-Compose-By-Example
С помощью Preview можно быстро посмотреть есть ли какой-то нужный вам пример или нет.
Кажется, стоит последить.
👍1
Виджет поиска на Compose. Часть 2. Финальная реализация.

Внимательный читатель, который уже повозился с Compose мог заметить, что код из первой части ничего кроме отображения не делал.
То бишь если попытаться что-то ввести в поле - ничего не происходит и текст не отображается.
Почему?
Все очень просто.
Состояние компонента никак не менялось. Соответственно recomposing не делался и повторно не отрисовывался виджет.
Исправить это очень просто. Добавляем состояние, которое будет привязано к введенному тексту:

val text = remember { mutableStateOf(TextFieldValue(value)) }
OutlinedTextField(
value = text.value,
onValueChange = {
text.value = it
textChangedCallback(it.text)
...
}

Все.
Теперь все отрисовывается при каждом вводе.

Давайте посмотрим на весь код и поговорим о реализации и некоторых нюансах:

@ExperimentalUnitApi
@Composable
fun SearchView(
modifier: Modifier = Modifier,
value: String = "",
hintText: String = "",
hintColor: Color = Color(0xFF797F90),
textStyle: TextStyle = TextStyle.Default,
contentDescription: String = "search widget",
cornerRadius: Dp = 12.dp,
showCancelButton: Boolean = false,
cancelButtonText: String = stringResource(R.string.search_view_cancel_button),
textChangedCallback: (String) -> Unit = {},
) {
val text = remember { mutableStateOf(TextFieldValue(value)) }
val focusManager = LocalFocusManager.current
Row(verticalAlignment = Alignment.CenterVertically) {
OutlinedTextField(
modifier = modifier,
value = text.value,
leadingIcon = {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.uicit_ic_search),
contentDescription = contentDescription,
)
},
trailingIcon = {
if (text.value.text.isNotEmpty()) {
Icon(
modifier = Modifier.clickable { text.value = TextFieldValue("") },
painter = painterResource(R.drawable.ic_clear),
contentDescription = contentDescription,
)
}
},
singleLine = true,
onValueChange = {
text.value = it
textChangedCallback(it.text)
},
shape = RoundedCornerShape(cornerRadius),
colors = TextFieldDefaults.outlinedTextFieldColors(
placeholderColor = hintColor,
textColor = Color(0xFF383B4F),
cursorColor = Color(0xFF6E7FFC),
focusedBorderColor = Color(0xFF6E7FFC),
unfocusedBorderColor = Color(0xFFDDDEE7),
),
placeholder = {
Text(
text = hintText,
style = TextStyle(fontSize = TextUnit(16.0f, TextUnitType.Sp))
)
},
textStyle = textStyle,
)
if (showCancelButton) {
Spacer(modifier = Modifier.padding(6.dp))
Text(
text = cancelButtonText,
color = Color(0xFF6E7FFC),
maxLines = 1,
modifier = Modifier.clickable {
focusManager.clearFocus()
}
)
}
}
}

Продолжение ниже.
Теперь тезисно о реализации.
* Во-первых, мы переехали с TextField на OutlinedTextField. Это нужно для того, чтобы при фокусе у нас менялся цвет рамки компонента. TextField по-умолчанию так не умеет.
* Далее, modificator мы вынесли во внешний параметр, как и говорили в конце прошлого поста. Компонент нужно и можно настраивать извне.
* Иконка крестика теперь появляется если введен какой-то текст. Вполне логично поведение. А реализовать такое поведение ну очень просто. Добавили нужное условие на text и все.
* По желанию дизайнера мы привнесли в Android чуточку iOS. Увы. Появилась опциональная кнопка Отмена, которая нужна для сбрасывания фокуса. По мне так дичь, но человек настоял. Якобы компоненты мы делаем одинаковыми на обеих платформах.
* Во внешние параметры мы вынесли стиль текста. К слову, стоит немного подрефакторить и вынести туда же настройку цветов. Можно даже передавать в функцию уже настроенный TextFieldColors.
* Для того чтобы компонент и кнопка были единым целым мы вложили его в Row и выровняли по вертикали.

Немного о фокусе ввода.
Как оказалось, есть с ним нюансы и просто так его убрать не получится. На помощь приходит LocalFocusManager и в клике на Отмена мы чистим фокус ввода + скрывается клавиатура.
На текущий момент ок, но фокус убирается у любого компонента, а не только нашего. Возможно с этим стоит повозиться.

В целом это кайф, когда с помощью кода ты можешь добавлять иконку, кастомизировать быстро любое поведение, да и при желании анимацию добавить не сложно.
Какой-то новый уровень создания UI, которого, кажется нам не хватало.
👍2
Завезли новую Android Stuidio Bumblebee.

Из интересного:
- теперь на девайсах с Android 11+ можно отлаживать приложения через Wi-Fi
- новые (более глубокие) возможности профилирования приложений, которые настраиваются через profileable тег в манифесте
- улучшенное превью для Compose
- новый Device Manager
- улучшенный Layout Inspector. Можно тестировать дизайн и делать снепшоты разных состояний (скриншоты) прямо оттуда.
- обновили базу (теперь на базе Idea 2021.1.1)
- немного поменяли процесс запуска тестов (по идеи должно быть быстрее?)

Уже качаю.

Почитать подробнее:
https://android-developers.googleblog.com/2022/01/android-studio-bumblebee-202111-stable.html
👍1
Как вам впечатления от новой студии?
Уже привыкли, что Emulator стал частью окна студии? 🙂
p.s.
Странные баги на Mac OS тоже присутствуют (как обычно), но в целом жить можно.

А вот Preview нормальный для Compose так и не завезли. Все равно build - refresh руками дергаем, в отличии от iOS / Flutter где все изменения сами подхватываются.
👍1
Старый Мобильщик
Как вам впечатления от новой студии? Уже привыкли, что Emulator стал частью окна студии? 🙂 p.s. Странные баги на Mac OS тоже присутствуют (как обычно), но в целом жить можно. А вот Preview нормальный для Compose так и не завезли. Все равно build - refresh…
Хм, а кстати завезли некий "Hot Reload" для Compose без повторных билдов. Меняешь размер шрифта и сразу изменение на экране эмуляторе. Хмм. Надо бы изучить, что еще может эта фича.
Другое дело!
👍1
Первое впечатление от Flutter

Поигрался несколько дней с Flutter.
Было интересно сделать какой-нибудь API-вызов, поиграться с виджетами, посмотреть как быстро получается что-то делать с нуля.

Кстати, для этого поста я поговорил с коллегой, который разработал еще год-два назад на аутсорс для британского заказчика приложение на Flutter. Скачал, поигрался с этим приложением - работает в целом шустро и каких-либо косяков особо не заметил. И, кстати, у него уже 1 млн установок и рейтинг 4.4, что довольно неплохо.

Что понравилось во Flutter:
1. Интуитивная простота. Для быстрого старта дока хороша и все нужное легко найти. Да и сам Flutter довольно дружелюбен для новичка, а всю доку можно просмотреть за полдня.
2. Куча готовых пакетов на pub.dev. Все еще есть, казалось бы простые, задачи для которых пакетов или их функциональности не хватает, но их количество явно растет с каждым годом.
3. Все - виджет. Даже разделитель во Flutter - это виджет.
4. Hot Reload - прекрасен. Меняете код и у вас обновляется приложение в эмуляторе. Это прям веб-приложение в мобильном мире!
5. Куча готовых виджетов на все случаи жизни: https://docs.flutter.dev/development/ui/widgets/layout
В данном случае, куча - в хорошем смысле!
6. Один UI на все платформы. И на Android, и на iOS, и на Web, и на Desktop.
7. Тут могу ошибаться, но во Flutter нельзя использовать рефлексию (скорее всего для производительности UI?) и именно поэтому парсить JSON-чик приходится несколько сложнее, чем вы привыкли. Нужно установить либу, настроить генератор кода для вашей модельки (благо не сложно), запустить и вуаля. Тут, конечно, все сложнее. Можно, обойтись и стандартными средствами, но будет больше кода и менее привычно после всяких GSON.
8. Плюшки языка. В Dart есть async / await, поддержка nullable-типов из коробки. Мелочь, а приятно.
9. Compose явно пилили с похожими мотивами и идеями Flutter. Ну или скорее Flutter пилили похожим на React / Redux. Изучая Flutter или Compose, становится проще в смежной теме.
10. Встроенный роутинг и механизм состояний, хотя есть множество сторонних либ. Одновременно и плюс и минус.

Что не очень понравилось:
1. В тоже время Dart - довольно странный язык, к которому нужно привыкнуть. Вроде бы не самый старый язык, но, например, везде в конце выражения требуется ; Не то, чтобы Dart вызывает какой-то особый негатив, если вы хорошо знакомы с Java, но забавные (странные) вещи встречаются. Плюс нужно закладывать время на его изучение.
2. Некоторые вещи делаются сложнее (тот же парсинг JSON), но некоторые и проще.
3. Отладка - отдельная песня к которой нужно привыкать. То ли еще не разобрался, но логов как-то не хватает. Для отладки, кстати, поднимается локальный сервер и там можно смотреть память и прочие вещи. Хорошо, что есть, но непривычно, что не в IDE.
4. Некоторые вещи не реализованы и нужно допиливать либо существующие плагины, либо писать самому. Для простых приложений, наверное, не очень критично.
5. Множество подходов по созданию архитектуры. Не то чтобы прям сильный минус, потому что тоже самое есть на любой платформе, но есть популярный BLoc-s, а можно обмазаться и стандартными классами State, а еще есть два-три популярных подхода. Тут нужно изучать детальнее каждый, но для начала воспользуемся стандартным и чуть позже уже BLoc.
6. Вложенность и вертикальный код. Виджет в виджет, в другой виджет и т.д. Во-первых, сильной вложенности нужно избегать в принципе, так как теряется производительность. Во-вторых, читать такой код сложнее, когда множество атрибутов и вертикальный код (как-нибудь покажу пример).
7. Популярность в Китае и Индии. Тут двоякая история. С одной стороны это не дает Гуглу закрыть проект (хотелось бы верить), с другой - сами понимаете. Качество проектов, которые придут к вам на поддержку, кода не всегда будет хорошим, как и качество сторонних плагинов (либ).

Вроде бы все.
Код и примерчики будут позже, как поправлю парсинг и доделаю главный экран.
👍2
Еще немного о виджете поиска на Compose

Посидев с дизайнером, посмотрев виджет поиска, я решил переделать основу для компонента. В текущем решении на базе OutlinedTextField не хватало гибкости. Например, нельзя задать отступы от текста до иконки.
Самое быстрое и правильное решение - использовать для основы BasicTextField.

У BasicTextField есть composable decorationBox который позволяет сделать любой сложности вьюшку вместе с полем ввода. Например, в нашем случае мы вложили поле ввода в Row и поместили по краям от текста иконки на нужном расстоянии (для читабельности код немного сократил):

decorationBox = { innerTextField ->
Row(
...
) {
Icon(
...
)
Spacer(modifier = Modifier.padding(start = 4.dp))
Box(contentAlignment = Alignment.CenterStart) {
if (showHint.value) {
Text(
text = hintText,
...
)
}
innerTextField() // наше поле ввода
}
if (text.value.text.isNotEmpty()) {
Icon(
...
)
}
}
}

В остальном, у нас особо ничего не поменялось по сравнению с предыдущей реализацией.
Единственное, это немного иначе реализована работа с фокусом и изменением цвета рамки. При получении фокуса мы меняем рамку у Row, который является контейнером для нашего поля innerTextField и всех вложенных вью:

Row(
modifier = Modifier.border(
if (isFocused) 2.dp else 1.dp,
animatedBorderColor,
shape = RoundedCornerShape(cornerRadius)
)
.defaultMinSize(minHeight = 40.dp)
.clipToBounds()
)

Полную версию закинул на GitHub: https://github.com/Djangist/ComposeSearchView/blob/main/SearchView.kt
Пользуйтесь.
👍1
Как-то рассказывал вам про Charles Proxy и тогда были восторженные отзывы об этом инструменте!
Но... однажды услышав про ProxyMan от коллеги, решил попробовать его в деле. Уже месяца 3 или 4 сижу на этом прекрасном проксике. И скажу вам: если у вас Mac - лучшего тулза для отладки приложения я еще не видел.

Почему вообще перешел на ProxyMan?
В основном, из-за не самого удобного интерфейса настройки локальных моков API в Charles (плюс там нужно постоянно с файлами возиться), а у нас в банке это довольно нужная и востребованная фича на которую не хочется тратить кучу времени. В ProxyMan поменять какой-то ответ можно буквально за пару кликов через контекстное меню мышкой и это круто! Экономит кучу времени.

Во-вторых, более интуитивный интерфейс и больше возможностей.
Например, буквально вчера решил посмотреть есть ли эмуляция медленной сети и потери пакетов.
И как вы думаете?
Да, есть. Опять же, экономия времени. Легко и просто проверил shimmer-анимацию или какие-то состояния ошибок.
И вы же понимаете главный прикол, да? Не нужно перезапускать приложение в эмуляторе или на девайсе! Меняешь ответы, состояния и тестируешь приложеньку.

И наверное, не самый последний аргумент - у ProxyMan есть бесплатная версия. У нее, естественно, есть ограничения, но пока каких-то проблем с ними для мобильщика не встретил. Да, ограничение в 4 домена, но этого хватает.

Отдельно хочу отметить удобный форматтер для Json и подсветка. Читать длинные ответы API оч удобно.

И конечно, прекрасная документация, которая не ради галочки.

Вообщем, маковадам рекомендую 👉 https://proxyman.io
Жаль, что не реклама 🙂
👍2