Вот несколько советов и extensions на Kotlin, которые будут полезны в ежедневной работе Android-разработчика.
Совет 1: Добивайтесь зелёной галочки
Android Studio предоставляет множество встроенных проверок: от юнит-тестов до линтёра. К сожалению, пока нет хорошего способа убедиться, что на CI нет предупреждений, поэтому мы разместили наш профиль для проверки в репозитории, чтобы все видели один и тот же набор предупреждений.
Затем мы взяли за правило ВСЕГДА избавляться от предупреждений (фиксить или заглушать их). При появлении новых предупреждений (из-за рефакторинга или добавления новых проверок), мы следуем правилу бойскаута (всегда оставляй после себя код в лучшем состоянии, чем до тебя).
Однако есть более сложный случай — точки входа, такие как фрагменты, на которые ссылаются только с помощью рефлексии (Android Studio помечает их как неиспользуемые). Чтобы избавиться от таких предупреждений мы сделали TonalEntryPoint.
Используя эту аннотацию в классе или конструкторе, который помечен как неиспользуемый, и нажимая alt+enter, мы получим возможность добавить неиспользуемый код в список точек входа.
Совет 2: Относитесь к предупреждениям Kotlin, как к ошибкам
Для этого добавим следующий код в файл build.gradle:
allprojects {
gradle.projectsEvaluated {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
allWarningsAsErrors = true
}
}
}
}
Совет 3: Делегат ViewBinding
Мы очень любим View Binding. Он отлично работает, и мы полностью перешли на него с ButterKnife/Kotlin Synthetics. Однако, синтаксис его создания не идеален, поэтому мы создали собственный делегат, чтобы создавать его в соответствии с жизненным циклом, это выглядит следующим образом:
class WifiNetworksFragment : TonalFragment(R.layout.wifi_networks_fragment) {
private val binding: WifiNetworksFragmentBinding by viewBinding()
...
}
class WifiNetworkView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
private val binding: WifiNetworkViewBinding by viewBinding()
...
}
Здесь можно посмотреть полный код.
Совет 4: fadeTo(visible)
Необходимость скрыть/показать view возникает часто. Для улучшения пользовательского опыта мы создали функцию fadeTo() в качестве замены для View.setVisibility() или View.isVisible = true/false.
// Функцию можно вызывать повторно, она не будет прерывать анимацию
myView.fadeTo(true)
myView.fadeTo(false)
myView.fadeTo(true, toAlpha = 0.8f)
myView.fadeTo(true, startDelay = 300)
myView.fadeTo(true, duration = 500)
Код функции можно найти на GitHub.
Совет 5: mapDistinct()
Это простое сокращение для Flow.map().distinctUntilChanged().
/**
* Сокращение map(...).distinctUntilChanged()
*/
fun <T, V> Flow.mapDistinct(mapper: suspend (T) -> V): Flow = map(mapper).distinctUntilChanged()
Совет 6: uniqueObservable()
Delegates.observable в Kotlin очень полезен. Однако:
- Иногда нам нужно делать что-то только при изменении значения.
- В таком случае писать лямбда-выражение с тремя параметрами и проверку на равенство чересчур шаблонно.
Поэтому мы написали uniqueObservable(), который будет вызывать лямбду только если значение изменится.
class Label @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
var inverted by uniqueObservable(false) { refreshDrawableState() }
...
}
Совет 7: Отладка + Корутины и Broadcast Receivers
В настоящее время Broadcast Receivers (широковещательные приёмники) редко используются в Android. Однако они могут быть крайне полезны для тестов в процессе отладки.
Мы используем debug-only широковещательные приемники, которые, по сути, дают нам CLI для установки/обновления параметров в коде без необходимости вызывать меню отладки в UI или пересобирать приложение.
Наш coroutine friendly код будет выглядеть так:
context.registerReceiverInScope(scope, WifiManager.WIFI_STATE_CHANGED_ACTION) { intent ->
val state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED)
// Use wifi state here
}
Совет 8: ConflatedJob
Корутины часто используются для отмены выполнения Job, перед запуском нового (например когда пользователь делает pull to refresh). ConflatedJob автоматически отменяет старый Job, перед запуском нового.
class MyClass(private val scope: CoroutineScope) {
private val job = ConflatedJob()
fun retry() {
retryJob += scope.launch {
delay(Long.MAX_VALUE)
}
}
}
Совет 9: Timber Property Delegate
Для логгирования мы используем Timber, однако его синтаксис Timber.tag(TAG).i(…) — слишком громоздкий.
По аналогии с View Binding Delegate, мы написали делегат, который упрощает вызов:
class MyClass {
private val log by timber()
fun logSomething() {
log.i("Hello")
log.w(Exception(), "World")
}
}
Он автоматически создает TAG на каждое свойство (а не на строку журнала, как Timber.DebugTree), сокращает общие суффиксы, такие как «Impl» и «ViewModel» и обрезает теги до 23 символов.
Полный код на GitHub.
Совет 10: Number.dp
Так как в макетах обычно используют dp, а в свойствах View пиксели, часто в коде требуется конвертировать dp в пиксели. Именно поэтому вопрос о том как это сделать пользуется популярностью на StackOverflow. Мы используем для этого следующий extension на Kotlin:
/**
* Вызовите эту функцию для dp и она вернет эквивалентное значение
* в пикселях для текущего дисплея.
* e.g. 8.dp
*/
val Number.dp get() = toFloat() * (Resources.getSystem().displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
С помощью этой функции очень просто обновить padding:
recyclerView.updatePadding(top = 14.dp.toInt())