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

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

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

Обсудить что-либо: @activitynotfound
Download Telegram
Channel photo updated
Пост 0. О себе и индексе массива

Представим, что вы в новой кампании. Примерно так выглядело бы ваше представление (немного утрированное) другим коллегам.
Звать вас Александром.
Вам 33 года уже (привет, Age-изм и все вытекающие?), с 2011 года вы занимаетесь только разработкой под Android. Полтора года вы руководили Android-командой удаленно из 5+2 человек и опыт этот даже был не плохой.
Повидали, как говорится, много разного Г. Ну и вы не из Москвы. Есть такой славный город Ростов-на-Дону.
Спросят: а что заканчивал? А вы гордо (нет) скажите Донской Государственный Технологический Университет, по специальности Компьютерная Безопасность. Хотя учились на коммерческой основе почти два года, пока не стукнуло заняться обучением и перейти на бюджетную основу. Одна из немногих ваших гордостей.
Обучились Android разработке сами, когда еще и загуглить-то по теме было особо нечего. Исходники и дедукция вам в помощь.
Начинали с Android 2.1 на эмуляторе. То еще, кстати. Удовольствие было.
Но все равно все хорошо, потому что была жажда новых знаний и желание писать качественный код. Тогда и без каки-либо курсов можно прекрасно справиться. Но хорошие коллеги - самое ценное, как ни крути.
Работали и работаете удаленно, хотя и в офисе потрудились.
Хороший опыт, хорошая ЗП (действительно, грех жаловаться) - казалось бы, что еще нужно?
Видимо писульки тут писать еще. Возможно, они кому-то будут полезными.
С этого и начнем, с нулевого. Вы же все знаете, что в нормальных языках индекс в коллекции всегда с нуля?
Старый Мобильщик pinned «Пост 0. О себе и индексе массива Представим, что вы в новой кампании. Примерно так выглядело бы ваше представление (немного утрированное) другим коллегам. Звать вас Александром. Вам 33 года уже (привет, Age-изм и все вытекающие?), с 2011 года вы занимаетесь…»
Пост 1. Charles Proxy

Charles - очень удобная штука для тех, кто работает с API. Для многих это не секрет!
Во-первых, легко можно просмотреть ответ от сервера, особенно, это удобно когда он довольно весомый и Logcat часть обрезает.
Во-вторых, виден запрос и все нужные параметры (заголовки, тело) - удобно отлаживать, когда вы пишите классы для работы с API.
В-третьих, ответ форматирован и его удобно просматривать.
В четвертых, все ответы сгруппированы по папкам. Это просто прекрасно.
В-пятых, запрос который выполняется подсвечивается цветом.
И самый весомый плюс (о котором мы поговорим в отдельном посте) - возможность мокать ответ от сервера (подменять на свои данные). Таким образом без единой строчки кода можно тестировать приложение.
И это далеко не все плюсы. Их гораздо больше.
Единственная засада - тулза платная и нужен ключик (про другие пути его использования упоминать не хочется)

Раньше было довольно муторно его настраивать, но теперь с Android Emulator можно все настроить за 3 минуты.
Поможет отличный гайд: https://mdapp.medium.com/the-android-emulator-and-charles-proxy-a-love-story-595c23484e02
Пост 2. Сказ о том, как Алеши в личке о смене дизайна договорились.

Друзья! Никогда не делайте так, как наши герои, если вы заинтересованы в долгой, продуктивной работе в команде и кампании в целом. 
Дело было так.
Утренний дейлик.
iOS-разработчик начал возмущаться на тему цвета и дизайнер сказал, что напишет ему в личку.
У меня эта задача уже была сделана. Но кто бы мог подумать, что дизайнер втихаря поменяет цвета?! Во время спринта. Без обновления Фигмы. И без сообщения в чате для всех заинтересованных разработчиков.
Потом ко мне в личку приходит тестеровщик и говорит "что-то цвета не совпадают с тем, что в дизайне".
Шикардос.
Сюжет прекрасный? И, наверное, классический для многих команд.
Так вот.
НЕ ДЕЛАЙТЕ ТАК НИКОГДА. ЕСЛИ ВЫ ОБСУЖДАЕТЕ ЧТО-ТО, ЧТО КАСАЕТСЯ ЕЩЕ КОГО-ЛИБО В ВАШЕЙ КОМАНДЕ - ОБСУЖДАЙТЕ ЭТО В ОБЩЕМ РАБОЧЕМ ЧАТЕ, ТЕГАЯ НУЖНОГО ЧЛЕНА КОМАНДЫ.
Такая простая истина, а на деле часто не выполняется и даже в 2021-ом от нее подгорает капитально.
Пост 3. ViewBindingPropertyDelegate

Неплохая обертка для удобной инициализации ViewBinding-а:
https://github.com/kirich1409/ViewBindingPropertyDelegate
для тех кому не нравится гугловое решение из доки, которое довольно многословно:

private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

Ccылка на доку откуда взят код: https://developer.android.com/topic/libraries/view-binding

Единственное, лучше использовать делегат без рефлексии (в либе отдельные зависимости на делегаты с рефлексией и без).
А если не хотите зависеть от дополнительной библиотеки, что сейчас почему-то становится трендом - скопируйте нужный делегат к себе в проект и поддерживайте как вам нравится.
Пост автора: https://proandroiddev.com/make-android-view-binding-great-with-kotlin-b71dd9c87719
Пост 4. Пахнущий код.

Что не так с этим кодом? 

Многие крутые разработчики на собеседованиях любят мусолить принципы 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), но поэкспериментировать с настройками можно.
Пост 6. Полезные extensions (часть 1)

Немного полезных 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ом! Так что ребята всегда следят за трендами и самое главное - применяют их на практике.

Ждем вторую часть!