Пост 4. Пахнущий код.
Что не так с этим кодом?
Многие крутые разработчики на собеседованиях любят мусолить принципы SOLID, но потом приходим к ним в кампанию и видим такой вот код, как на скрине.
Ощущение что:
- либо никто эти принципы не понимает
- либо это старый код
- либо принцип осознанно не используется, ибо обычно задачи сделать нужно еще вчера и лишние телодвижения делать никому не хочется
Что из этого в нашем случае - сложно сказать. История проекта мне неведома, но такие простые вещи уже можно было давно поправить.
Данный код нарушает "Принцип разделения интерфейса», который гласит: много интерфейсов, специально предназначенных для клиентов, лучше, чем один интерфейс общего назначения. Самое интересное, что подобные конвертеры в нескольких местах проекта.
Исправить ситуацию относительно просто: для каждой модели мы создаем свой конвертер с одним методом и используем эти зависимости в нужных местах.
Что еще тут плохо?
Конечно же, название интерфейса. Что за DataConverter? Название, которое не говорит вообще ничего. Какие данные мы конвертируем. Куда? Во что?
И напоследок еще несколько замечаний.
Зачем проверять типы таким вот странным образом (цикл + continue)? Возможно, код сконвертирован из Java, но в Kotlin есть метод filterIsInstance и другие более лаконичные варианты. В данном случае же, код выглядит довольно запутанным.
Следующий нюанс - это строка:
val list: MutableList<Product> = ArrayList()
Есть функция mutableListOf(), которая во всех статьях только и мелькает. Еще красивее можно было б сделать через listOf() и apply() / run().
Что мне еще не нравится здесь - это использование Locale. Зависимость, которая не передается (не инжектится через DI) в конструктор, а используется напрямую. При чем зависимость вообще, которой тут быть не должно.
Такой маленький кусочек кода, а набрали столько проблем. Зато все эти принципы спрашивают и очень очень ждут, чтобы рассказали их смысл. Может чтобы самим разобраться заодно?
Что не так с этим кодом?
Многие крутые разработчики на собеседованиях любят мусолить принципы SOLID, но потом приходим к ним в кампанию и видим такой вот код, как на скрине.
Ощущение что:
- либо никто эти принципы не понимает
- либо это старый код
- либо принцип осознанно не используется, ибо обычно задачи сделать нужно еще вчера и лишние телодвижения делать никому не хочется
Что из этого в нашем случае - сложно сказать. История проекта мне неведома, но такие простые вещи уже можно было давно поправить.
Данный код нарушает "Принцип разделения интерфейса», который гласит: много интерфейсов, специально предназначенных для клиентов, лучше, чем один интерфейс общего назначения. Самое интересное, что подобные конвертеры в нескольких местах проекта.
Исправить ситуацию относительно просто: для каждой модели мы создаем свой конвертер с одним методом и используем эти зависимости в нужных местах.
Что еще тут плохо?
Конечно же, название интерфейса. Что за DataConverter? Название, которое не говорит вообще ничего. Какие данные мы конвертируем. Куда? Во что?
И напоследок еще несколько замечаний.
Зачем проверять типы таким вот странным образом (цикл + continue)? Возможно, код сконвертирован из Java, но в Kotlin есть метод filterIsInstance и другие более лаконичные варианты. В данном случае же, код выглядит довольно запутанным.
Следующий нюанс - это строка:
val list: MutableList<Product> = ArrayList()
Есть функция mutableListOf(), которая во всех статьях только и мелькает. Еще красивее можно было б сделать через listOf() и apply() / run().
Что мне еще не нравится здесь - это использование Locale. Зависимость, которая не передается (не инжектится через DI) в конструктор, а используется напрямую. При чем зависимость вообще, которой тут быть не должно.
Такой маленький кусочек кода, а набрали столько проблем. Зато все эти принципы спрашивают и очень очень ждут, чтобы рассказали их смысл. Может чтобы самим разобраться заодно?
Пост 5. Способы ускорить сборку проекта
Печальнее ничего нет, чем сидеть и ждать пока соберется проект 5-10, а то и более минут.
Но процесс можно ускорить! Это сэкономит кучу вашего времени, а еще кучу денег кампании в который вы работаете.
Самый простой вариант - нормальное железо мы опустим в 2021-ом году. Хотя цены на комплектующие значительно выросли.
Рассмотрим только способы, которые можно применить здесь и сейчас. Как говорится с тем, что имеем.
Самый лучший вариант - гульнуть на почти все ОЗУ (на самом деле 4гб вполне достаточно даже для средних по величине проектов) и дать Gradle и самой Android Studio больше памяти.
Как это сделать?
В корне проекта есть файлик gradle.properties.
Нужно добавить строку:
org.gradle.jvmargs=-Xmx1536m //4g, 2g тоже будет работать
Обычно, эта строка и так есть - можно просто раскомментировать и добавить нужное количество ОЗУ.
Дальше стоит посмотреть на настройки Android Studion (см скриншот) и выставить максимальные, если позволяет ОЗУ. Если не позволяет, то билдить проекты вы будете долго, хотя наличие быстрого SSD тоже прекрасно помогает в этом.
Общие советы:
1. Используйте векторные ресурсы
2. Используйте формат WebP для графики, которую нельзя конвертировать в вектор (в Android Studio, кстати, есть опция Convert to WebP)
3. Обновите все плагины (чем новее тем лучше): Gradle Plugin, Android Plugin, Kotlin
4. Не забывайте обновлять библиотеки
5. Оптимизируйте ресурсы (часто в проекте много неиспользуемых ресурсов и ресурсов для каждой плотности экрана)
6. Используйте Gradle Cache
7. Используйте offline сборку (на любителя, потому что иногда бывают проблемы с либами, но обычно тоже ускоряет немного процесс сборки)
8. Если не можете поменять железо - можно присмотреться к внешнему SSD (даже они помогут ускорить сборку)
9. Используйте R8
10. Обновите ваш JDK. У JDK 9 версии более модный Garbage Collector, который еще и параллелится при выполнении. Чем версия JDK новее тем лучше (совместимая с Android SDK). У нас в проекте используется далеко не новая и это сильно влияет на скорость сборки.
11. Периодически чистите ваш код от мусора и старых, неиспользуемых классов.
12. Не используйте или используйте минимум рефлексию и библиотеки, основанные на ней
Не плохой конфиг для gradle.properties:
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.caching=true
android.enableBuildScriptClasspathCheck=false
Есть еще опция: org.gradle.workers.max=2
Но пока не могу сказать насколько она помогает. Кому помогло - делитесь опытом.
Отличная сборная солянка советов здесь (on English):
https://medium.com/linedevth/build-your-android-app-faster-and-smaller-than-ever-25f53fdd3cdc
Не все советы отсюда мне нравятся (например Instant Run), но поэкспериментировать с настройками можно.
Печальнее ничего нет, чем сидеть и ждать пока соберется проект 5-10, а то и более минут.
Но процесс можно ускорить! Это сэкономит кучу вашего времени, а еще кучу денег кампании в который вы работаете.
Самый простой вариант - нормальное железо мы опустим в 2021-ом году. Хотя цены на комплектующие значительно выросли.
Рассмотрим только способы, которые можно применить здесь и сейчас. Как говорится с тем, что имеем.
Самый лучший вариант - гульнуть на почти все ОЗУ (на самом деле 4гб вполне достаточно даже для средних по величине проектов) и дать Gradle и самой Android Studio больше памяти.
Как это сделать?
В корне проекта есть файлик gradle.properties.
Нужно добавить строку:
org.gradle.jvmargs=-Xmx1536m //4g, 2g тоже будет работать
Обычно, эта строка и так есть - можно просто раскомментировать и добавить нужное количество ОЗУ.
Дальше стоит посмотреть на настройки Android Studion (см скриншот) и выставить максимальные, если позволяет ОЗУ. Если не позволяет, то билдить проекты вы будете долго, хотя наличие быстрого SSD тоже прекрасно помогает в этом.
Общие советы:
1. Используйте векторные ресурсы
2. Используйте формат WebP для графики, которую нельзя конвертировать в вектор (в Android Studio, кстати, есть опция Convert to WebP)
3. Обновите все плагины (чем новее тем лучше): Gradle Plugin, Android Plugin, Kotlin
4. Не забывайте обновлять библиотеки
5. Оптимизируйте ресурсы (часто в проекте много неиспользуемых ресурсов и ресурсов для каждой плотности экрана)
6. Используйте Gradle Cache
7. Используйте offline сборку (на любителя, потому что иногда бывают проблемы с либами, но обычно тоже ускоряет немного процесс сборки)
8. Если не можете поменять железо - можно присмотреться к внешнему SSD (даже они помогут ускорить сборку)
9. Используйте R8
10. Обновите ваш JDK. У JDK 9 версии более модный Garbage Collector, который еще и параллелится при выполнении. Чем версия JDK новее тем лучше (совместимая с Android SDK). У нас в проекте используется далеко не новая и это сильно влияет на скорость сборки.
11. Периодически чистите ваш код от мусора и старых, неиспользуемых классов.
12. Не используйте или используйте минимум рефлексию и библиотеки, основанные на ней
Не плохой конфиг для gradle.properties:
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.caching=true
android.enableBuildScriptClasspathCheck=false
Есть еще опция: org.gradle.workers.max=2
Но пока не могу сказать насколько она помогает. Кому помогло - делитесь опытом.
Отличная сборная солянка советов здесь (on English):
https://medium.com/linedevth/build-your-android-app-faster-and-smaller-than-ever-25f53fdd3cdc
Не все советы отсюда мне нравятся (например Instant Run), но поэкспериментировать с настройками можно.
Пост 6. Полезные extensions (часть 1)
Немного полезных Kotlin extensions, которыми мы активно пользовались в предыдущем проекте.
Как этим пользоваться?
Очень просто. Любое расширение становится как бы методом класса для которого оно написано. Особенно удобно, когда расширение пишется для базового класса иерархии.
Пару примеров вызова:
Если еще не перешли на Kotlin (да, такое бывает и в 2021-ом), то вот вам дополнительный стимул. Код гораздо короче и лаконичнее.
Немного полезных Kotlin extensions, которыми мы активно пользовались в предыдущем проекте.
//Context.kt
inline val View.layoutInflater
get() = context.layoutInflater
inline val Context.layoutInflater: LayoutInflater
get() = LayoutInflater.from(this)
inline fun Context.copyToClipboard(data: String, label: String?) {
val clipData = ClipData.newPlainText(label, data)
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(clipData)
}
inline fun Context.isAppAvailable(appName: String?): Boolean {
return try {
packageManager.getPackageInfo(appName, PackageManager.GET_ACTIVITIES)
true
} catch (e: Exception) {
false
}
}
//DialogFragment.kt
inline fun DialogFragment.show(fragmentManager: FragmentManager?, vararg args: Pair<String, Any?>) {
if (args.isNotEmpty()) {
arguments = bundleOf(*args)
}
fragmentManager?.let {
val tag = this::class.java.simpleName
if (it.findFragmentByTag(tag) == null) {
show(it, tag)
}
}
}
inline fun DialogFragment.show(fragment: Fragment?, vararg args: Pair<String, Any?>) {
show(fragment?.parentFragmentManager, *args)
}
//View.kt
inline fun View.showKeyboard(requestFocus: Boolean = false, forceShow: Boolean = false) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
if (imm != null) {
if (requestFocus) requestFocus()
val flags = if (forceShow) InputMethodManager.SHOW_FORCED else InputMethodManager.SHOW_IMPLICIT
imm.showSoftInput(this, flags)
}
}
fun View.hideKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(windowToken, 0)
}
inline fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
}
fun View.visibleIf(condition: Boolean) {
this.visibility = if (condition) View.VISIBLE else View.GONE
}
inline fun View.visible() {
this.visibility = View.VISIBLE
}
inline fun View.invisible() {
this.visibility = View.INVISIBLE
}
inline fun View.gone() {
this.visibility = View.GONE
}
Как этим пользоваться?
Очень просто. Любое расширение становится как бы методом класса для которого оно написано. Особенно удобно, когда расширение пишется для базового класса иерархии.
Пару примеров вызова:
someEditTextView.showKeyboard()
…
// например так мы показываем BottomSheet
fun showNow(fragment: Fragment) = SomeSheetDialog().apply {
setTargetFragment(fragment, R.id.request_code)
show(fragment) // наш extensions
}
Если еще не перешли на Kotlin (да, такое бывает и в 2021-ом), то вот вам дополнительный стимул. Код гораздо короче и лаконичнее.
RedMadRobot решили проверить "хаотичное" обучение с нуля на примере Kotlin Coroutines:
https://www.youtube.com/watch?v=cHERit7LNGM
Пробежались по основам и немного рассказали про StateFlow и SharedFlow.
В целом полезное видео для тех, кто собирается переходить на корутины. Есть сравнение корутиновских флоу с Rx флоу. И особенно радует, что ребята рассказали на каком уровне использовать скоупы и диспатчеры в контексте современной архитектуры на базе ViewModel в качестве presentation-слоя.
Впрочем это можно почитать самостоятельно из главной статьи, которая постоянно упоминается в видео:
https://developer.android.com/kotlin/coroutines/coroutines-best-practices
Мне как человеку, которому довелось поработать в команде RedMadRobot всегда приятно смотреть за их развитием. И именно в RMR я начал использовать впервые Котлин, кажется в 2017ом! Так что ребята всегда следят за трендами и самое главное - применяют их на практике.
Ждем вторую часть!
https://www.youtube.com/watch?v=cHERit7LNGM
Пробежались по основам и немного рассказали про StateFlow и SharedFlow.
В целом полезное видео для тех, кто собирается переходить на корутины. Есть сравнение корутиновских флоу с Rx флоу. И особенно радует, что ребята рассказали на каком уровне использовать скоупы и диспатчеры в контексте современной архитектуры на базе ViewModel в качестве presentation-слоя.
Впрочем это можно почитать самостоятельно из главной статьи, которая постоянно упоминается в видео:
https://developer.android.com/kotlin/coroutines/coroutines-best-practices
Мне как человеку, которому довелось поработать в команде RedMadRobot всегда приятно смотреть за их развитием. И именно в RMR я начал использовать впервые Котлин, кажется в 2017ом! Так что ребята всегда следят за трендами и самое главное - применяют их на практике.
Ждем вторую часть!
Давайте проведем небольшой интерактивчик.
Сколько лет вы занимаетесь Android-разработкой?
Сколько лет вы занимаетесь Android-разработкой?
Anonymous Poll
64%
Только начинаю / начинающий (менее года)
24%
1-3 года
6%
3-5 лет
6%
Более 5 лет
Пост 7. Неявное в Data-классах Kotlin
Data-классы - идеальный инструмент для создания моделей на Domain и Data-слоях (по сути любых моделей данных).
Если вы знакомы только с Java, то самая простая аналогия - это Java-класс с геттерами и сеттерами, для которого реализован стандартный паттерн hashCode() - equals() и toString(). Kotlin реализовывает это все за нас, что делает код невероятно компактным!
Но нужно помнить одну важную вещь.
Только поля, которые переданы в конструктор Data-класса будут добавлены в дефолтную реализацию паттерна hashCode() - equals().
То бишь если объявить класс так - все будет хорошо - все поля попадут в реализацию:
Но если тот же класс объявить так:
В реализацию hashCode() и equals() от Kotlin не попадет поле age. Об этом нужно помнить. Особенно во время ревью Pull Requests ваших коллег.
Еще у Data-классов есть классный метод copy(), который очень часто выручает. Делает по-прежнему работу с данными immutable, но позволяет менять какие-то данные при создании копии объекта:
Говоря простым языком: если у вас есть объект, поля которого заполняются поэтапно (например, большая форма/экран, которую(ый) пользователь может заполнить частично) - copy() прекрасный вариант сделать это безопасно и элегантно.
Про Data-классы в sealed-классах поговорим отдельно.
Ссылка на оф доку: https://kotlinlang.org/docs/data-classes.html
Data-классы - идеальный инструмент для создания моделей на Domain и Data-слоях (по сути любых моделей данных).
Если вы знакомы только с Java, то самая простая аналогия - это Java-класс с геттерами и сеттерами, для которого реализован стандартный паттерн hashCode() - equals() и toString(). Kotlin реализовывает это все за нас, что делает код невероятно компактным!
Но нужно помнить одну важную вещь.
Только поля, которые переданы в конструктор Data-класса будут добавлены в дефолтную реализацию паттерна hashCode() - equals().
То бишь если объявить класс так - все будет хорошо - все поля попадут в реализацию:
data class User(val name: String, val age: Int)
Но если тот же класс объявить так:
data class User(val name: String) {
var age: Int = 0
}
В реализацию hashCode() и equals() от Kotlin не попадет поле age. Об этом нужно помнить. Особенно во время ревью Pull Requests ваших коллег.
Еще у Data-классов есть классный метод copy(), который очень часто выручает. Делает по-прежнему работу с данными immutable, но позволяет менять какие-то данные при создании копии объекта:
recipeData = recipeData?.copy(sourceName = sourceName, sourceDisplayName = sourceName, sourceUrl = sourceUrl)
Говоря простым языком: если у вас есть объект, поля которого заполняются поэтапно (например, большая форма/экран, которую(ый) пользователь может заполнить частично) - copy() прекрасный вариант сделать это безопасно и элегантно.
Про Data-классы в sealed-классах поговорим отдельно.
Ссылка на оф доку: https://kotlinlang.org/docs/data-classes.html
Kotlin Help
Data classes | Kotlin
Старый Мобильщик pinned «Давайте проведем небольшой интерактивчик.
Сколько лет вы занимаетесь Android-разработкой?»
Сколько лет вы занимаетесь Android-разработкой?»
Что новенького в рассылках #1
Добрался до своих еженедельных рассылок и решил поревьювить - что вообще интересного есть.
Спойлер: не особо много.
Разве что...
Любопытная либа-надстройка над Retrofit от Slack:
https://github.com/slackhq/EitherNet
Позволит упростить процесс обработки ошибок от бекенда, используя sealed-классы Kotlin вместо обработки исключений. Особых проблем с обработкой исключений вроде не замечено, но с sealed-классами более красиво выходит.
и...
Любопытная статья на тему настройки CI поверх GithubActions:
https://proandroiddev.com/continuous-integration-delivery-for-android-with-github-actions-part-1-b232ed2b1740
Прогонет тесты и сбилдит apk-шку после пуша в репу. Может пригодится если ваша репа на Github.
Было еще пару примеров очередных архитектур, хайпов на тему Kotlin Multiplatform и примерчик игры "Змейка" на Compose for Desktop - вообщем, все как обычно.
Добрался до своих еженедельных рассылок и решил поревьювить - что вообще интересного есть.
Спойлер: не особо много.
Разве что...
Любопытная либа-надстройка над Retrofit от Slack:
https://github.com/slackhq/EitherNet
Позволит упростить процесс обработки ошибок от бекенда, используя sealed-классы Kotlin вместо обработки исключений. Особых проблем с обработкой исключений вроде не замечено, но с sealed-классами более красиво выходит.
и...
Любопытная статья на тему настройки CI поверх GithubActions:
https://proandroiddev.com/continuous-integration-delivery-for-android-with-github-actions-part-1-b232ed2b1740
Прогонет тесты и сбилдит apk-шку после пуша в репу. Может пригодится если ваша репа на Github.
Было еще пару примеров очередных архитектур, хайпов на тему Kotlin Multiplatform и примерчик игры "Змейка" на Compose for Desktop - вообщем, все как обычно.
GitHub
GitHub - slackhq/EitherNet: A multiplatform, pluggable, and sealed API result type for modeling network API responses.
A multiplatform, pluggable, and sealed API result type for modeling network API responses. - slackhq/EitherNet
Пост 8. Моки в Charles
Я в восторге от Charles Proxy!
Работая в такой, местами очень не гибкой сфере (мягко говоря), как банковская - постоянно сталкиваешься с какими-то трудностями при отладке. Всегда может внезапно отвалиться бекенд (довольно часто в момент вашего багфикса) и как раз в таких ситуациях моки приходят нам на помощь.
Настроить их очень просто.
1. Сохраните себе в текстовый файл ответ от сервера, который будете подставлять в Charles (лучше сделать несколько заготовок с разными данными)
2. Выбираете Tools - Rewrite. Откроется окно в котором нужно будет добавить новое правило и поставить галочку Enable Rewrite.
3. При создании правила нужно выбрать тип Body (мокаем ответ от сервера), ставим галочку Response и в поле Value подставляем сохраненный ответ от сервера.
4. В окне Location нужно добавить URL-адрес вашего бекенда и путь к вызову метода.
5. По сути все. Дважды нажимаем Ок, в эмуляторе делаем нужный нам запрос (в моем случае обновляем данные главного экрана) и мок будет подставляться.
Даже эмулятор перезапускать не нужно! Всего три минуты и можно тестировать и отлаживать.
Главное, не забудьте потом убрать галочку Enable Rewrite, чтобы снова вернуть реальный ответ от сервера.
Самое прекрасное в этом - вы не теряете время и можете быстро тестировать работу приложения, подменяя нужные данные. Наглядно всплывают все косяки.
Чуть подробнее можно почитать, например, здесь:
https://medium.com/@migueloruiz/charles-tips-and-tricks-mocking-dfddf0ffaadd
Я в восторге от Charles Proxy!
Работая в такой, местами очень не гибкой сфере (мягко говоря), как банковская - постоянно сталкиваешься с какими-то трудностями при отладке. Всегда может внезапно отвалиться бекенд (довольно часто в момент вашего багфикса) и как раз в таких ситуациях моки приходят нам на помощь.
Настроить их очень просто.
1. Сохраните себе в текстовый файл ответ от сервера, который будете подставлять в Charles (лучше сделать несколько заготовок с разными данными)
2. Выбираете Tools - Rewrite. Откроется окно в котором нужно будет добавить новое правило и поставить галочку Enable Rewrite.
3. При создании правила нужно выбрать тип Body (мокаем ответ от сервера), ставим галочку Response и в поле Value подставляем сохраненный ответ от сервера.
4. В окне Location нужно добавить URL-адрес вашего бекенда и путь к вызову метода.
5. По сути все. Дважды нажимаем Ок, в эмуляторе делаем нужный нам запрос (в моем случае обновляем данные главного экрана) и мок будет подставляться.
Даже эмулятор перезапускать не нужно! Всего три минуты и можно тестировать и отлаживать.
Главное, не забудьте потом убрать галочку Enable Rewrite, чтобы снова вернуть реальный ответ от сервера.
Самое прекрасное в этом - вы не теряете время и можете быстро тестировать работу приложения, подменяя нужные данные. Наглядно всплывают все косяки.
Чуть подробнее можно почитать, например, здесь:
https://medium.com/@migueloruiz/charles-tips-and-tricks-mocking-dfddf0ffaadd
Пост 9. Полезные extensions (часть 2)
Сегодня у нас будет целая пачка мелких, но полезных на практике extensions. В основном по работе с разными ресурсами.
Обратите внимание на аннотации. Лучше всегда помечать нужные типы ресурсов подобными аннотациями, чтобы и среда и различные линтеры (вроде Lint, KtLint) различали в каком случае у вас именно int, а в каком случае Int ссылается на ресурс цвета. Подобные утилиты помогут пройтись по коду и составить список потенциальных проблем, а среда их подсветит в виде Warnings при сборке проекта.
А аннотации Nullable / NotNull пригодятся еще и в смешанном проекте (в котором и Kotlin и Java).
Почитать подробнее про аннотации можно здесь:
https://developer.android.com/studio/write/annotations
Сегодня у нас будет целая пачка мелких, но полезных на практике extensions. В основном по работе с разными ресурсами.
inline fun Fragment?.dimenPx(@DimenRes resId: Int): Int {
return this?.context?.resources?.getDimensionPixelSize(resId) ?: 0
}
inline fun Fragment?.dimen(@DimenRes resId: Int): Float {
return this?.context?.resources?.getDimension(resId) ?: 0f
}
inline fun View.dimen(@DimenRes resId: Int): Float = resources.getDimension(resId)
inline fun View.dimenPx(@DimenRes resId: Int): Int = resources.getDimensionPixelSize(resId)
inline fun View.color(@ColorRes resId: Int): Int = ContextCompat.getColor(context, resId)
inline fun View.drawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId)
inline fun Context.dimen(@DimenRes resId: Int): Float = resources.getDimension(resId)
inline fun Context.dimenPx(@DimenRes resId: Int): Int = resources.getDimensionPixelSize(resId)
inline fun Context.color(@ColorRes resId: Int): Int = ContextCompat.getColor(this, resId)
inline fun Context.drawable(@DrawableRes resId: Int?): Drawable? = resId?.let { ContextCompat.getDrawable(this, it) }
inline fun View.getString(@StringRes resId: Int): String = context.getString(resId)
inline fun Fragment.color(@ColorRes resId: Int): Int =
context?.let { ContextCompat.getColor(it, resId) } ?: Color.TRANSPARENT
inline fun Fragment.drawable(@DrawableRes resId: Int): Drawable? =
context?.let { ContextCompat.getDrawable(it, resId) }
inline fun Fragment.font(@FontRes resId: Int): Typeface? = context?.font(resId)
inline fun View.font(@FontRes resId: Int) = context.font(resId)
inline fun Context.font(@FontRes resId: Int): Typeface? = ResourcesCompat.getFont(this, resId)
inline fun Context.fontSpan(@FontRes resId: Int): TypefaceSpanCompat? = font(resId)?.getSpan()
inline val Context?.screenWidth: Int; get() = this?.resources?.displayMetrics?.widthPixels ?: 0
inline val Context?.screenHeight: Int; get() = this?.resources?.displayMetrics?.heightPixels ?: 0
Обратите внимание на аннотации. Лучше всегда помечать нужные типы ресурсов подобными аннотациями, чтобы и среда и различные линтеры (вроде Lint, KtLint) различали в каком случае у вас именно int, а в каком случае Int ссылается на ресурс цвета. Подобные утилиты помогут пройтись по коду и составить список потенциальных проблем, а среда их подсветит в виде Warnings при сборке проекта.
А аннотации Nullable / NotNull пригодятся еще и в смешанном проекте (в котором и Kotlin и Java).
Почитать подробнее про аннотации можно здесь:
https://developer.android.com/studio/write/annotations
Android Developers
Improve code inspection with annotations | Android Studio | Android Developers
Learn how annotations let you provide hints to code inspections tools like lint to help detect subtle code problems.
Пост 10. О полезных иногда EventBus-ах
Очередной пост вышел довольно длинным, поэтому оформил его через Телеграф.
Если такой формат зайдет - будем использовать чаще.
Думаю многие уже знают или со временем столкнуться с шинами или EventBus-ами. Сейчас, по большому счету - это антипаттерн.
Если совсем упрощать, то суть вот в чем: кто-то кидает события, а кто-то на них подписывается.
https://telegra.ph/Post-10-O-poleznyh-inogda-EventBus-ah-04-16-2
Очередной пост вышел довольно длинным, поэтому оформил его через Телеграф.
Если такой формат зайдет - будем использовать чаще.
Думаю многие уже знают или со временем столкнуться с шинами или EventBus-ами. Сейчас, по большому счету - это антипаттерн.
Если совсем упрощать, то суть вот в чем: кто-то кидает события, а кто-то на них подписывается.
https://telegra.ph/Post-10-O-poleznyh-inogda-EventBus-ah-04-16-2
Telegraph
Пост 10. О полезных иногда EventBus-ах
DroidDevNotes Думаю многие уже знают или со временем столкнуться с шинами или EventBus-ами. Сейчас, по большому счету - это антипаттерн. Если совсем упрощать, то суть вот в чем: кто-то кидает события, а кто-то на них подписывается. Те кто давно в Android…
У RedMadRobot вышла вторая часть хаотичного изучения Coroutines.
На этот раз ребята разобрали:
- Как же создавать корутины в Data и Domain слоях
- coroutineScope и supervisorScope
- Job
- Deferred и async/await
- Использование внешнего скоупа
- Как создать внешний скоуп
- Избегай GlobalScope
- CoroutineScope
- CoroutineContext
- CoroutineScope vs CoroutineContext
- Делай свои корутины cancellable
- Помни про исключения
https://youtu.be/6Apj_v9ZkBs
На этот раз ребята разобрали:
- Как же создавать корутины в Data и Domain слоях
- coroutineScope и supervisorScope
- Job
- Deferred и async/await
- Использование внешнего скоупа
- Как создать внешний скоуп
- Избегай GlobalScope
- CoroutineScope
- CoroutineContext
- CoroutineScope vs CoroutineContext
- Делай свои корутины cancellable
- Помни про исключения
https://youtu.be/6Apj_v9ZkBs
YouTube
Coroutines. Хаотичное изучение. Часть 2
Продолжение "хаотичного изучения" Kotlin Coroutines. 👨🌾
В этот раз обсудили такие советы и темы:
- Как же создавать корутины в Data и Domain слоях
- coroutineScope и supervisorScope
- Job
- Deferred и async/await
- Использование внешнего скоупа
- Как создать…
В этот раз обсудили такие советы и темы:
- Как же создавать корутины в Data и Domain слоях
- coroutineScope и supervisorScope
- Job
- Deferred и async/await
- Использование внешнего скоупа
- Как создать…
Что новенького в рассылках #2
Вышел Kotlin 1.5.0-RC.
Из интересного:
* доводят до ума (стабилизируют) беззнаковые целые типы UInt, ULong, UByte, UShort, которые появились в Kotlin 1.3 Beta.
* Extensions для пакета java.nio.file.Path в kotlin.io.path
* Накидали разных полезных функций для Char: Char.isDigit(), Char.isLetter(), Char.isLowerCase(), Char.isUpperCase(), Char.isTitleCase()
* Для более удобной работы с остатками и делением добавили функции floorDiv() и mod()
* Методы коллекций firstNotNullOf() и firstNotNullOfOrNull(), которые можно теперь юзать вместо двух методов mapNotNull() и first()
Познавательная статья на тему компиляции в Art
https://proandroiddev.com/android-runtime-how-dalvik-and-art-work-6e57cf1c50e5
И в ней же любопытная ссылка на доку: https://source.android.com/devices/tech/dalvik/configure#how_art_works которая даже ценнее исходной статьи.
Будете готовиться к собеседованиям - самое-то! Иногда спрашивают как это все работает и в каких версиях Android были серьезные изменения. Сложно сказать зачем это спрашивать в условной Яндекс.Еде, но думаю отдельно поговорим об этом.
Очередной пример использования Compose: https://github.com/GuilhE/KitchenTimer в рамках Гуглового #AndroidDevChallenge
Выглядит это все удручающе, если честно (скрин к посту). Привет, макароны. Хороший пример как не надо писать код. Хотя для участия в event-е от Гугла наверное норм. Если не забуду - набросаем более вменяемый примерчик с архитектурой. Уже давно хочется самому поковырять Compose!
Mark Allison снова пишет про DataStore: https://blog.stylingandroid.com/datastore-1-0-0-alpha08/
Вот весь цикл статей: https://blog.stylingandroid.com/category/jetpack/datastore/
DataStore - хранилище для сохранения пар ключ-значение или типизированных объектов на базе протокола ProtoBuf. DataStore использует Coroutines и Flow, но привязки есть и для RxJava. Если юзаете SharedPreferences - можно потихоньку в отдельной ветке мигрировать на DataStore. Правда все еще alpha08, а Гугловые альфы могут идти годами.
Если такой формат вам полезен - пишите в комментах, что узнали или хотите поизучать!
Вышел Kotlin 1.5.0-RC.
Из интересного:
* доводят до ума (стабилизируют) беззнаковые целые типы UInt, ULong, UByte, UShort, которые появились в Kotlin 1.3 Beta.
* Extensions для пакета java.nio.file.Path в kotlin.io.path
* Накидали разных полезных функций для Char: Char.isDigit(), Char.isLetter(), Char.isLowerCase(), Char.isUpperCase(), Char.isTitleCase()
* Для более удобной работы с остатками и делением добавили функции floorDiv() и mod()
* Методы коллекций firstNotNullOf() и firstNotNullOfOrNull(), которые можно теперь юзать вместо двух методов mapNotNull() и first()
Познавательная статья на тему компиляции в Art
https://proandroiddev.com/android-runtime-how-dalvik-and-art-work-6e57cf1c50e5
И в ней же любопытная ссылка на доку: https://source.android.com/devices/tech/dalvik/configure#how_art_works которая даже ценнее исходной статьи.
Будете готовиться к собеседованиям - самое-то! Иногда спрашивают как это все работает и в каких версиях Android были серьезные изменения. Сложно сказать зачем это спрашивать в условной Яндекс.Еде, но думаю отдельно поговорим об этом.
Очередной пример использования Compose: https://github.com/GuilhE/KitchenTimer в рамках Гуглового #AndroidDevChallenge
Выглядит это все удручающе, если честно (скрин к посту). Привет, макароны. Хороший пример как не надо писать код. Хотя для участия в event-е от Гугла наверное норм. Если не забуду - набросаем более вменяемый примерчик с архитектурой. Уже давно хочется самому поковырять Compose!
Mark Allison снова пишет про DataStore: https://blog.stylingandroid.com/datastore-1-0-0-alpha08/
Вот весь цикл статей: https://blog.stylingandroid.com/category/jetpack/datastore/
DataStore - хранилище для сохранения пар ключ-значение или типизированных объектов на базе протокола ProtoBuf. DataStore использует Coroutines и Flow, но привязки есть и для RxJava. Если юзаете SharedPreferences - можно потихоньку в отдельной ветке мигрировать на DataStore. Правда все еще alpha08, а Гугловые альфы могут идти годами.
Если такой формат вам полезен - пишите в комментах, что узнали или хотите поизучать!
Друзья, какие темы вам сейчас наиболее интересны? (можно выбрать несколько)
Anonymous Poll
33%
Хотелось бы примерный план обучения
33%
Все что касается Android Dev (пиши обо всем)
39%
Архитектура приложения, presentation-слой (MVP, MVVM, MVI)
22%
Больше контента про Kotlin и его основы
17%
Соскучились по нытью из трудовых будней
17%
Coroutines
22%
Хайповый Compose + примерчик
33%
Верстка, кастомные вьюшки
22%
Dependency Injection: Dagger/Hilt, Kodein, Koin, Toothpick
0%
Иное (напишу в комменты)
Пост 11. С чего начать новичку. Краткий план обучения: основы
Собрал свои мысли в кучку и написал примерный план обучения для новичков (старичкам тоже полезно повторять иногда гайды - скоро поговорим об этом тоже). Сложность в том, что у всех разный опыт и универсальный план подобрать довольно сложно. Будем исходить из того, что человек совсем ничего не знает о платформе или знает совсем немного.
И так, поехали.
1. Архитектура Android-приложения. Роль jvm, как устроено приложение, процессы в Android.
2. Компоненты в Android. Стоит понимать какие существуют основные компоненты и какой у них жизненный цикл.
3. AndroidManifest.xml - основной конфиг приложения. Как объявлять активити, интент фильтры, сервисы, пермишены и т.д.
4. Activity - фундамент любого приложения. Жизненный цикл и основные методы.
5. Fragment - строительные кирпичики приложения. Жизненный цикл и основные методы.
6. Сейчас чаще всего архитектура приложения строится на базе "паттерна" Single Activity (одна Activity и множество Fragment) и крайне полезно понимать как взаимодействуют Acitivty и Fragment и как пересекаются их жизненные циклы. Тут же: FragmentTransaction, различия методов add() и replace(), commit() / commitNow(), FragmentManager. По сути, многие роутеры (тот же Cicherone) за кулисами работают именно с этими классами.
7. Tasks и Back Stack, навигация между экранами, флаги влияющие на поведение в стеке
8. Context - основной класс для доступа к ресурсам (и основной источник утечек памяти), Intent, IntentFilter, Bundle, запуск активити и других приложений. Здесь же полезно разобрать как не надо работать с Context: context leaks
9. Основные типы ресурсов и папки для них (res/values, res/layout, res/drawable, assets и т.д.)
10. Service. Жизненный цикл и основные методы, типы сервисов
11. Views: основные виджеты в Android (TextView, EditText, ImageView, View и т.д.)
12. Базовые ViewGroups: базовые контейнеры вьюшек (LinearLayout, RelativeLayout, FrameLayout)
13. RecyclerView: списки, сетки (гриды) и прочее - это все про него. Штука мощная, поэтому стоит отдельного внимания. Сюда же можно добавить RecyclerView Adapters, ViewHolders, Layout Managers.
14. ConstraintLayout. Самый мощный ViewGroup с помощью которого можно сверстать почти что угодно. Также выделен отдельно, ибо невероятно мощный, с кучей возможностей. Говорят, что пришел к нам с iOS.
15. Кастомные Views. Как создать, жизненный цикл, методы onMeasure, onLayout, парсинг атрибутов (attrs.xml) - помнить про метод recycle()
16. Animations. Виды анимаций, метод view.animate(), векторная анимация, ObjectAnimator / ValueAnimator, android:animateLayoutChanges, а в качестве бонуса можно рассмотреть библиотеку Lottie, которая часто используется для сложной векторной анимации (сплеш-скрины, красивые разные штуки)
17. Стили и темы: кастомизируем внешний вид приложения
18. Toolbar, Menus, BottomNavigationView, ViewPager / ViewPager2
19. Контейнеры для прокрутки: ScrollView, HorizontalScrollView, NestedScrollView, полезно знать возможные проблемы с NestedScrollView
20. Dialogs: Dialog, DialogFragment
21. BottomSheets, BottomSheetBehavior, CoordinatorLayout, BottomSheetDialog, BottomSheetDialogFragment. Приложения без BottomSheets сейчас сложно встретить
22. Permissions: типы, правила прозрачности и минимального количества пермишенов
23. SharedPreferences, DataStore (опционально)
24. BroadcastReceiver - встроенный механизм "event bus" в Android: ограничения, типы, класс LocalBroadcastManager.
25. Handler, Looper, HandlerThread
26. На закуску: ContentProvider - CRUD-интерфейс для работы со встроенными БД в Android (контакты, календарь и прочее)
Пошаговый гайд по созданию вашего первого приложения:
https://developer.android.com/training/basics/firstapp
Другие гайды:
https://developer.android.com/guide
У Гугла есть еще CodeLabs: https://developer.android.com/courses/fundamentals-training/toc-v2 - тоже полезная штука. Можно скачать код, поиграться с ним и все это в пошаговом туториале.
Собрал свои мысли в кучку и написал примерный план обучения для новичков (старичкам тоже полезно повторять иногда гайды - скоро поговорим об этом тоже). Сложность в том, что у всех разный опыт и универсальный план подобрать довольно сложно. Будем исходить из того, что человек совсем ничего не знает о платформе или знает совсем немного.
И так, поехали.
1. Архитектура Android-приложения. Роль jvm, как устроено приложение, процессы в Android.
2. Компоненты в Android. Стоит понимать какие существуют основные компоненты и какой у них жизненный цикл.
3. AndroidManifest.xml - основной конфиг приложения. Как объявлять активити, интент фильтры, сервисы, пермишены и т.д.
4. Activity - фундамент любого приложения. Жизненный цикл и основные методы.
5. Fragment - строительные кирпичики приложения. Жизненный цикл и основные методы.
6. Сейчас чаще всего архитектура приложения строится на базе "паттерна" Single Activity (одна Activity и множество Fragment) и крайне полезно понимать как взаимодействуют Acitivty и Fragment и как пересекаются их жизненные циклы. Тут же: FragmentTransaction, различия методов add() и replace(), commit() / commitNow(), FragmentManager. По сути, многие роутеры (тот же Cicherone) за кулисами работают именно с этими классами.
7. Tasks и Back Stack, навигация между экранами, флаги влияющие на поведение в стеке
8. Context - основной класс для доступа к ресурсам (и основной источник утечек памяти), Intent, IntentFilter, Bundle, запуск активити и других приложений. Здесь же полезно разобрать как не надо работать с Context: context leaks
9. Основные типы ресурсов и папки для них (res/values, res/layout, res/drawable, assets и т.д.)
10. Service. Жизненный цикл и основные методы, типы сервисов
11. Views: основные виджеты в Android (TextView, EditText, ImageView, View и т.д.)
12. Базовые ViewGroups: базовые контейнеры вьюшек (LinearLayout, RelativeLayout, FrameLayout)
13. RecyclerView: списки, сетки (гриды) и прочее - это все про него. Штука мощная, поэтому стоит отдельного внимания. Сюда же можно добавить RecyclerView Adapters, ViewHolders, Layout Managers.
14. ConstraintLayout. Самый мощный ViewGroup с помощью которого можно сверстать почти что угодно. Также выделен отдельно, ибо невероятно мощный, с кучей возможностей. Говорят, что пришел к нам с iOS.
15. Кастомные Views. Как создать, жизненный цикл, методы onMeasure, onLayout, парсинг атрибутов (attrs.xml) - помнить про метод recycle()
16. Animations. Виды анимаций, метод view.animate(), векторная анимация, ObjectAnimator / ValueAnimator, android:animateLayoutChanges, а в качестве бонуса можно рассмотреть библиотеку Lottie, которая часто используется для сложной векторной анимации (сплеш-скрины, красивые разные штуки)
17. Стили и темы: кастомизируем внешний вид приложения
18. Toolbar, Menus, BottomNavigationView, ViewPager / ViewPager2
19. Контейнеры для прокрутки: ScrollView, HorizontalScrollView, NestedScrollView, полезно знать возможные проблемы с NestedScrollView
20. Dialogs: Dialog, DialogFragment
21. BottomSheets, BottomSheetBehavior, CoordinatorLayout, BottomSheetDialog, BottomSheetDialogFragment. Приложения без BottomSheets сейчас сложно встретить
22. Permissions: типы, правила прозрачности и минимального количества пермишенов
23. SharedPreferences, DataStore (опционально)
24. BroadcastReceiver - встроенный механизм "event bus" в Android: ограничения, типы, класс LocalBroadcastManager.
25. Handler, Looper, HandlerThread
26. На закуску: ContentProvider - CRUD-интерфейс для работы со встроенными БД в Android (контакты, календарь и прочее)
Пошаговый гайд по созданию вашего первого приложения:
https://developer.android.com/training/basics/firstapp
Другие гайды:
https://developer.android.com/guide
У Гугла есть еще CodeLabs: https://developer.android.com/courses/fundamentals-training/toc-v2 - тоже полезная штука. Можно скачать код, поиграться с ним и все это в пошаговом туториале.
Android Developers
Create your first Android app | Android Developers
Learn how to create your first Android app.
👍1
Примеры_задач_для_подготовки_к_алгоритмам.pdf
33.5 KB
Кстати, по поводу собеседований.
Если вдруг кто-то захочет пообщаться с Яндекс вот вам примеры алгоритмических задачек, которые любезно предоставляют их hr-ы, чтобы подготовиться к собеседованию.
До сих пор не понимаю до конца зачем в Яндекс.Еде спрашивать всякие алгоритмические задачки, но дело Яндекса. Видимо чтобы бегать из проекта в проект и писать сходу супер-оптимизированный код. А может просто как обычно копируют Гугл? Хз.
Решать их в качестве хобби или зарядки для мозгов полезно - никто не спорит, а на практике - что-то действительно сложное, где нужны хорошие знания алгоритмов встречаются ну оооочень редко. Во всяком случае в обычных бизнес-приложения, коих 95%. И все это можно сесть за день разобрать с помощью инета или хорошей книги если хотя бы какие-то базовые знания есть. Да даже если и нет - множество классных книг уже выпущено. Бери и делай!
А чаще всего все проблемы приложения связаны с бекендом и работой с данными на самом клиенте.
Вообщем, такое, но может кому-то пригодится.
Если вдруг кто-то захочет пообщаться с Яндекс вот вам примеры алгоритмических задачек, которые любезно предоставляют их hr-ы, чтобы подготовиться к собеседованию.
До сих пор не понимаю до конца зачем в Яндекс.Еде спрашивать всякие алгоритмические задачки, но дело Яндекса. Видимо чтобы бегать из проекта в проект и писать сходу супер-оптимизированный код. А может просто как обычно копируют Гугл? Хз.
Решать их в качестве хобби или зарядки для мозгов полезно - никто не спорит, а на практике - что-то действительно сложное, где нужны хорошие знания алгоритмов встречаются ну оооочень редко. Во всяком случае в обычных бизнес-приложения, коих 95%. И все это можно сесть за день разобрать с помощью инета или хорошей книги если хотя бы какие-то базовые знания есть. Да даже если и нет - множество классных книг уже выпущено. Бери и делай!
А чаще всего все проблемы приложения связаны с бекендом и работой с данными на самом клиенте.
Вообщем, такое, но может кому-то пригодится.
Пост 12. UseCases vs Interactors (часть 1)
Начнем с небольшой теории.
UseСase и Interactor - сущности уровня domain, в которых обычно сосредоточена вся бизнес-логика приложения. Во всяком случае, стоит к этому стремиться.
Зачем это все?
Причин использовать юскейсы или интеракторы множество, но вот основные на мой взгляд.
1. Не писать бизнес-логику во вьюмоделях / презентерах. Тут можно снова вспомнить SOLID, но если говорить проще - код должен быть слабосвязным, чтобы его было проще поддерживать, тестировать и вот это вот все. Гораздо проще менять сущности, которые не сильно связаны между собой. Сегодня ViewModels / Presenters, завтра придумают или захотят ваши лиды что-то другое и когда логика отделена - поменять будет гораздо проще.
2. Бизнес-логику возможно придется использовать повторно в нескольких местах или даже в разных проектах. Мы же не будем использовать целый презентер, который всегда тесно связан с конкретным экраном в другом? Нет.
3. Во вьюмоделях и презентерах должна быть логика presеentation-слоя, то бишь всякие навигации, подсунуть данные во вьюшку, получить их из интерактора или юскейса.
4. Код читабельнее и проще. На самом деле важный пункт, который будет еще важнее по мере роста проекта и по мере добавления новых разработчиков в команду.
5. Можно разделять задачи между несколькими разработчиками. Кто-то пишет бизнес-логику, кто-то вьюмодели / презентеры или кто-то делает одни юйскейсы / интеракторы, кто-то другие. В итоге идет быстрее работа и меньше конфликтов в коде.
Забавно, что для простоты примеров Гугл в своей доке часто делает вызовы методов репозитория прямо во вьюмоделях. А еще смешнее, что подобное потом встречаешь в реальном проекте. Человек просто скопировал из доки, не задумываясь. И это кстати брокерское приложение!
Разница между ними не такая большая и часто на уровне вкусовщины конкретной команды.
Однако, в примерах Гугла часто можно встретить именно UseCases.
В чем на самом деле разница?
1. Количество файлов. В большом проекте будет очень много UseCases, что со временем будет довольно сложно поддерживать. Некоторые юскейсы можно объединить в одном kt-файле, конечно, но все равно от их огромного количества не спастись.
2. UseCase - это класс с одним методом, который часто лишь дергает один единственный метод репозитория (хорошее правило), но понятное дело, что там может быть (и должна) любая бизнес-логика. В интерфейсе интерактора может быть множество методов, но все они должны работать с одной сущностью. То бишь удовлетворять принципу S из SOLID. Но UseCases в этом плане еще лучше, ибо причина для изменения юскейса обычно всего одна, в отличии от интерактора. Плюс интерактор не совсем удовлетворяет принципу I.
3. UseCases более наглядны. Посмотрите скриншот - по названиям юскейсов уже можно примерно понять, что происходит в проекте. С интеракторами дело чуть сложнее.
На самом деле, сложно быть объективным - обычно я всегда использую интеракторы и лишь не так давно в своем проекте начал рефакторить на юскейсы, чтобы просто посмотреть разницу. Если давать осмысленные имена для юскейсов, то получается вцелом наглядно.
Начнем с небольшой теории.
UseСase и Interactor - сущности уровня domain, в которых обычно сосредоточена вся бизнес-логика приложения. Во всяком случае, стоит к этому стремиться.
Зачем это все?
Причин использовать юскейсы или интеракторы множество, но вот основные на мой взгляд.
1. Не писать бизнес-логику во вьюмоделях / презентерах. Тут можно снова вспомнить SOLID, но если говорить проще - код должен быть слабосвязным, чтобы его было проще поддерживать, тестировать и вот это вот все. Гораздо проще менять сущности, которые не сильно связаны между собой. Сегодня ViewModels / Presenters, завтра придумают или захотят ваши лиды что-то другое и когда логика отделена - поменять будет гораздо проще.
2. Бизнес-логику возможно придется использовать повторно в нескольких местах или даже в разных проектах. Мы же не будем использовать целый презентер, который всегда тесно связан с конкретным экраном в другом? Нет.
3. Во вьюмоделях и презентерах должна быть логика presеentation-слоя, то бишь всякие навигации, подсунуть данные во вьюшку, получить их из интерактора или юскейса.
4. Код читабельнее и проще. На самом деле важный пункт, который будет еще важнее по мере роста проекта и по мере добавления новых разработчиков в команду.
5. Можно разделять задачи между несколькими разработчиками. Кто-то пишет бизнес-логику, кто-то вьюмодели / презентеры или кто-то делает одни юйскейсы / интеракторы, кто-то другие. В итоге идет быстрее работа и меньше конфликтов в коде.
Забавно, что для простоты примеров Гугл в своей доке часто делает вызовы методов репозитория прямо во вьюмоделях. А еще смешнее, что подобное потом встречаешь в реальном проекте. Человек просто скопировал из доки, не задумываясь. И это кстати брокерское приложение!
Разница между ними не такая большая и часто на уровне вкусовщины конкретной команды.
Однако, в примерах Гугла часто можно встретить именно UseCases.
В чем на самом деле разница?
1. Количество файлов. В большом проекте будет очень много UseCases, что со временем будет довольно сложно поддерживать. Некоторые юскейсы можно объединить в одном kt-файле, конечно, но все равно от их огромного количества не спастись.
2. UseCase - это класс с одним методом, который часто лишь дергает один единственный метод репозитория (хорошее правило), но понятное дело, что там может быть (и должна) любая бизнес-логика. В интерфейсе интерактора может быть множество методов, но все они должны работать с одной сущностью. То бишь удовлетворять принципу S из SOLID. Но UseCases в этом плане еще лучше, ибо причина для изменения юскейса обычно всего одна, в отличии от интерактора. Плюс интерактор не совсем удовлетворяет принципу I.
3. UseCases более наглядны. Посмотрите скриншот - по названиям юскейсов уже можно примерно понять, что происходит в проекте. С интеракторами дело чуть сложнее.
На самом деле, сложно быть объективным - обычно я всегда использую интеракторы и лишь не так давно в своем проекте начал рефакторить на юскейсы, чтобы просто посмотреть разницу. Если давать осмысленные имена для юскейсов, то получается вцелом наглядно.
Пост 12. UseCases vs Interactors (часть 2)
Пара примеров:
И интерактор:
А что с тестированием?
Конечно, проще писать тесты на юскейсы просто потому что они меньше, они делают ровно одно действие и соответственно их проще замокать.
Хорошая статья про юскейсы: https://proandroiddev.com/why-you-need-use-cases-interactors-142e8a6fe576
Классический пример архитектуры куда можно иногда подсматривать: https://github.com/android10/Android-CleanArchitecture
А что обычно используете вы?
Пишите в комменты свой опыт работы с юскейсами или интеракторами. И что на ваш взгляд лучше, удобнее и разные нюансы из опыта.
Пара примеров:
// базовый интерфейс юскейса
interface BaseUseCase<in Params, out Type> where Type : Any {
suspend operator fun invoke(params: Params): Type
}
class SuggestionCityUseCase @Inject constructor(
private val repository: AddressSuggestionRepository
) : BaseUseCase<AddressRequest, SuggestionsData>t; {
override suspend fun invoke(params: AddressRequest): SuggestionsData {
return repository.addressSuggest(params.query)
}
}
// кстати, operator fun invoke() позволит вызывать код юскейса так:
suggestionUseCase(AddressRequest(query = query))
И интерактор:
interface SettingsAccountInteractor {
suspend fun getUserInfo(force: Boolean): UserAccount
fun getUserInfoSync(): UserAccount?
fun enableLocalization(): Boolean
}
class SettingsAccountInteractorImpl @Inject constructor(
private val userRepository: UserRepository,
private val config: Config
) : SettingsAccountInteractor {
override suspend fun getUserInfo(force: Boolean): UserAccount = userRepository.getUserInfo(force)
override fun getUserInfoSync(): UserAccount? = userRepository.getUserInfoSync()
override fun enableLocalization(): Boolean = config.enableLocalization
}А что с тестированием?
Конечно, проще писать тесты на юскейсы просто потому что они меньше, они делают ровно одно действие и соответственно их проще замокать.
Хорошая статья про юскейсы: https://proandroiddev.com/why-you-need-use-cases-interactors-142e8a6fe576
Классический пример архитектуры куда можно иногда подсматривать: https://github.com/android10/Android-CleanArchitecture
А что обычно используете вы?
Пишите в комменты свой опыт работы с юскейсами или интеракторами. И что на ваш взгляд лучше, удобнее и разные нюансы из опыта.
Medium
Why you need use-cases/interactors
Since Clean Architecture became the new hot topic in the Android world there have been loads of code samples and articles that tried to…
Что новенького в рассылках #3
Выбор девайса для работы всегда актуален. Небольшое сравнение скорости компиляции и нагрева Apple M1 с i7:
https://proandroiddev.com/apple-m1-vs-intel-the-ultimate-comparison-2a2f0d197dc
Разница впечатляет!
Compose. Тут как всегда куча всего.
Понеслась!
Начали клепать либы для Compose. Вот, например, либа для пунктов настроек:
https://github.com/alorma/Compose-Settings
Очередной пример использования Compose:
https://github.com/rock3r/Bundel/
Хороший пошаговый туториал по созданию списка с данными на Compose для начинающих:
https://www.waseefakhtar.com/android/recyclerview-in-jetpack-compose/
И еще один:
https://medium.com/mateedevs/bye-xml-it-was-nice-knowing-you-pt-1-50b195bab1a9
Пост с официального блога. Крис Бейнс сравнил разницу в размере приложения (количество строк + размер APK) и скорости сборки после миграции на Compose:
https://medium.com/androiddevelopers/jetpack-compose-before-and-after-8b43ba0b7d4f
И кстати, в репозитории Криса можно увидеть архитектуру вместе с Compose (ViewModels, навигация и прочее) - https://github.com/chrisbanes/tivi
Рекомендую!
Сравнение LiveData и Flow:
https://proandroiddev.com/flow-livedata-what-are-they-best-use-case-lets-build-a-login-system-39315510666d
А вы уже начали менять свои лайвдаты на StateFlow?
В Room добавили auto-migrations:
https://medium.com/androiddevelopers/room-auto-migrations-d5370b0ca6eb
Сравнение композиции и наследования в Kotlin:
https://kt.academy/article/ek-composition
Пример мультиплатформенного аппа:
https://github.com/joreilly/StarWars
Либа-сахарок для инициализации Android Lifecycle-aware свойств:
https://github.com/skydoves/Lazybones
Выбор девайса для работы всегда актуален. Небольшое сравнение скорости компиляции и нагрева Apple M1 с i7:
https://proandroiddev.com/apple-m1-vs-intel-the-ultimate-comparison-2a2f0d197dc
Разница впечатляет!
Compose. Тут как всегда куча всего.
Понеслась!
Начали клепать либы для Compose. Вот, например, либа для пунктов настроек:
https://github.com/alorma/Compose-Settings
Очередной пример использования Compose:
https://github.com/rock3r/Bundel/
Хороший пошаговый туториал по созданию списка с данными на Compose для начинающих:
https://www.waseefakhtar.com/android/recyclerview-in-jetpack-compose/
И еще один:
https://medium.com/mateedevs/bye-xml-it-was-nice-knowing-you-pt-1-50b195bab1a9
Пост с официального блога. Крис Бейнс сравнил разницу в размере приложения (количество строк + размер APK) и скорости сборки после миграции на Compose:
https://medium.com/androiddevelopers/jetpack-compose-before-and-after-8b43ba0b7d4f
И кстати, в репозитории Криса можно увидеть архитектуру вместе с Compose (ViewModels, навигация и прочее) - https://github.com/chrisbanes/tivi
Рекомендую!
Сравнение LiveData и Flow:
https://proandroiddev.com/flow-livedata-what-are-they-best-use-case-lets-build-a-login-system-39315510666d
А вы уже начали менять свои лайвдаты на StateFlow?
В Room добавили auto-migrations:
https://medium.com/androiddevelopers/room-auto-migrations-d5370b0ca6eb
Сравнение композиции и наследования в Kotlin:
https://kt.academy/article/ek-composition
Пример мультиплатформенного аппа:
https://github.com/joreilly/StarWars
Либа-сахарок для инициализации Android Lifecycle-aware свойств:
https://github.com/skydoves/Lazybones
Приятные новости подъехали!
В этом году (18-20 мая) Google IO пройдет онлайн и бесплатно для всех.
Ссылочка со счетчиком: https://events.google.com/io/?lng=en
Добавляйте к себе в календарь, чтобы не забыть.
В этом году (18-20 мая) Google IO пройдет онлайн и бесплатно для всех.
Ссылочка со счетчиком: https://events.google.com/io/?lng=en
Добавляйте к себе в календарь, чтобы не забыть.
Пост 13. Вытаскиваем аргументы фрагмента в одну строчку с помощью delegated properties
Магия Котлина помогает писать более лаконичный код, который можно легко повторно использовать в новых проектах. Например, можно воспользоваться delegated properties и написать небольшой, но удобный сахарок для получения аргументов фрагмента прямиком в поле класса всего одной строчкой:
Обычно, маленькие функции и функции-расширения (extensions) стоит помечать как inline. Особенно выигрыш происходит для extension с параметром в виде блока замыкания, где благодаря встраиванию не создаются дополнительные классы-обертки. Однако стоит помнить, что количество встраиваемых функций все равно ограничено и абсолютно все функции помечтать встраиваемыми нет смысла.
Ключевое слово reified позволяет нам подставить тип без использования рефлексии ("овеществлять") и может быть использовано только во встроенных (inline) функциях. Нет рефлексии - код безопаснее и чуть быстрее.
Функция вытаскивает из класса Bundle значение по ключу, либо подставляет значение по-умолчанию если ключа нет. В конце проверяет тип, который передается в функцию с типом значение, которое получено по ключу. На случай если передаете Int, а вытащить хотите Double.
Поехали дальше.
Теперь нам нужен делегат, который реализует интерфейс ReadWriteProperty (реализует операторы для присваивания и получения значений).
И собирает все воедино функция argument, которая как раз возвращает наш делегат:
Больше примеров использования делегируемых свойств в официальной доке:
https://kotlinlang.org/docs/delegated-properties.html#providing-a-delegate
Магия Котлина помогает писать более лаконичный код, который можно легко повторно использовать в новых проектах. Например, можно воспользоваться delegated properties и написать небольшой, но удобный сахарок для получения аргументов фрагмента прямиком в поле класса всего одной строчкой:
private val phone: String by argument(ARG_PHONE)
Давайте напишем для начала функцию, которая будет вытаскивать из поля arguments значения по ключу:inline fun <reified T> extractFromBundle(
bundle: Bundle?,
key: String,
defaultValue: T? = null
): T {
val result = bundle?.get(key) ?: defaultValue
if (result != null && result !is T) {
throw ClassCastException("Property $key has different class type")
}
return result as T
}
Ключевое слово inline как подсказка компилятору, что мы хотим сделать функцию встраиваемой (тело функции встраивалось при вызове целиком).Обычно, маленькие функции и функции-расширения (extensions) стоит помечать как inline. Особенно выигрыш происходит для extension с параметром в виде блока замыкания, где благодаря встраиванию не создаются дополнительные классы-обертки. Однако стоит помнить, что количество встраиваемых функций все равно ограничено и абсолютно все функции помечтать встраиваемыми нет смысла.
Ключевое слово reified позволяет нам подставить тип без использования рефлексии ("овеществлять") и может быть использовано только во встроенных (inline) функциях. Нет рефлексии - код безопаснее и чуть быстрее.
Функция вытаскивает из класса Bundle значение по ключу, либо подставляет значение по-умолчанию если ключа нет. В конце проверяет тип, который передается в функцию с типом значение, которое получено по ключу. На случай если передаете Int, а вытащить хотите Double.
Поехали дальше.
Теперь нам нужен делегат, который реализует интерфейс ReadWriteProperty (реализует операторы для присваивания и получения значений).
class BundleExtractorDelegate<R, T>(private val initializer: (R) -> T) : ReadWriteProperty<R, T> {
private object EMPTY
private var value: Any? = EMPTY
override fun setValue(thisRef: R, property: KProperty<*>, value: T) {
this.value = value
}
override fun getValue(thisRef: R, property: KProperty<*>): T {
if (value == EMPTY) {
value = initializer(thisRef)
}
@Suppress("UNCHECKED_CAST")
return value as T
}
}
Здесь ничего особенного. Реализация интерфейса делегата в котором мы будем вызывать нашу написанную функцию.И собирает все воедино функция argument, которая как раз возвращает наш делегат:
inline fun <reified T> argument(
key: String,
defaultValue: T? = null
): ReadWriteProperty<Fragment, T> = BundleExtractorDelegate { thisRef ->
extractFromBundle(
bundle = thisRef.arguments,
key = key,
defaultValue = defaultValue
)
}
Мы передаем в первый параметр типа класс Fragment и можем обратиться к полю arguments, а далее вся нужная работа происходит в функции extractFromBundle. Больше примеров использования делегируемых свойств в официальной доке:
https://kotlinlang.org/docs/delegated-properties.html#providing-a-delegate
Kotlin Help
Delegated properties | Kotlin