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

Функция высшего порядка  —  это функция, которая принимает одну или несколько функций в качестве аргументов или возвращает функцию в качестве результата. В Kotlin можно определять функции высшего порядка с помощью лямбда-выражений, анонимных функций и именованных функций.

Далее рассмотрим, как функции высшего порядка используются в проектах Android.

Слушатели событий

Слушатели событий  —  распространенный случай использования функций высшего порядка при разработке Android. Рассмотрим следующую функцию, которая принимает в качестве аргумента функцию высшего порядка для обработки события нажатия кнопки:

fun View.onClick(action: () -> Unit) {
setOnClickListener { action() }
}

Эта функция расширяет класс View и принимает лямбда-выражение, которое будет выполняться при нажатии по представлению (view). Для настройки слушателя нажатия для представления используется метод setOnClickListener, а лямбда-выражение выполняется, когда происходит событие нажатия.

Используя эту функцию, можно обрабатывать события нажатия кнопки кратким и читабельным способом:

button.onClick { 
Toast.makeText(context, "Button clicked", Toast.LENGTH_SHORT).show()
}

Здесь мы передаем лямбда-выражение, которое выводит сообщение toast при нажатии на кнопку. Функция onClick устанавливает слушатель нажатий для кнопки и отвечает за выполнение лямбда-выражения при наступлении события нажатия.

Анимация представлений

Анимация представлений является распространенной задачей при разработке Android. Ее можно сделать многоразовой и более гибкой, используя функции высшего порядка. Ниже приведен пример функции расширения для класса View, которая задействует функции высшего порядка для анимации различных свойств:

fun View.animateProperty(
property: KProperty0<Float>,
fromValue: Float,
toValue: Float,
duration: Long,
onComplete: () -> Unit = {}
) {
val animator = ObjectAnimator.ofFloat(this, property.name, fromValue, toValue).apply {
setDuration(duration)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
onComplete()
}
})
}
animator.start()
}

Эта функция анимирует одно свойство представления (например, его координату X или Y) от начального значения до конечного в течение заданной продолжительности. Она также принимает опциональную функцию обратного вызова, которая будет выполнена по завершении анимации.

Можно использовать эту функцию в коде следующим образом:

view.animateProperty(
View.TRANSLATION_X,
fromValue = 0f,
toValue = 100f,
duration = 500,
onComplete = { onAnimationComplete() }
)

Этот код анимирует свойство перевода X объекта View из текущего положения (0) в новое (100) в течение 500 миллисекунд. По завершении анимации будет вызвана функция onAnimationComplete().

RecyclerView

RecyclerView  —  это широко используемый компонент пользовательского интерфейса в приложениях Android для отображения списка или сетки элементов. Он настраивается с помощью различных менеджеров расположения и адаптеров для конкретных случаев использования. В контексте функций высшего порядка RecyclerView можно использовать вместе с такими функциями для обеспечения большей гибкости и модульности кода.

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

fun <T> RecyclerView.bindData(
data: List<T>,
layoutRes: Int,
bindFunc: (View, T) -> Unit,
clickListener: ((T) -> Unit)? = null
) {
adapter = object : RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
bindFunc(holder.itemView, item)
clickListener?.let { listener ->
holder.itemView.setOnClickListener { listener(item) }
}
}
override fun getItemCount() = data.size
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
}

В этой функции data  —  список элементов, которые будут отображаться в RecyclerView, layoutRes  —  ID ресурса расположения для каждого элемента представления, bindFunc  —  функция, которая привязывает каждый элемент к соответствующему держателю представления, а clickListener  —  опциональная функция, обрабатывающая события нажатия по элементу. Функция возвращает адаптер RecyclerView, настроенный на отображение элементов с использованием предоставленного расположения и функции привязки.

Эта функция позволяет создать RecyclerView и заполнить его данными с помощью одного вызова функции, как показано ниже:

recyclerView.bindData(
data = listOf("item1", "item2", "item3"),
layoutRes = R.layout.list_item,
bindFunc = { view, item -> view.findViewById<TextView>(R.id.text_view).text = item },
clickListener = { item -> onItemClick(item) }
)

Этот код создает RecyclerView, устанавливает его данные в список строк и использует функцию привязки для установки текста каждого элемента представления в соответствующую строку. Он также устанавливает слушатель нажатий, который вызывает функцию onItemClick с элементом нажатия.

Потоковая обработка

Потоковая обработка  —  распространенный случай применения функций высшего порядка во многих языках программирования. Функция высшего порядка может быть использована для упрощения выполнения кода в отдельном потоке, что облегчает обработку асинхронных операций.

Например, в Kotlin можно определить функцию, которая принимает лямбду в качестве параметра и выполняет ее в фоновом потоке. Эту функцию можно использовать для того, чтобы избежать блокировки потока пользовательского интерфейса и обеспечить более плавное взаимодействие с пользователем. Вот пример:

fun <T> runOnBackgroundThread(backgroundFunc: () -> T, callback: (T) -> Unit) {
val handler = Handler(Looper.getMainLooper())
Thread {
val result = backgroundFunc()
handler.post { callback(result) }
}.start()
}

Эта функция принимает в качестве параметров две функции: backgroundFunc, которая выполняет высокозатратный расчет в фоновом потоке, и callback, вызывающую результат расчета в основном потоке.

Функция runOnBackgroundThread использует Handler для отправки функции callback в основной поток после того, как backgroundFunc завершит свою работу в отдельном потоке.

Эта функция высшего порядка позволяет выполнять высокозатратные вычисления в фоновом потоке и обновлять пользовательский интерфейс с результатом в основном потоке, не заботясь о нюансах потоковой обработки.

Например, можно вызвать функцию runOnBackgroundThread следующим образом:

runOnBackgroundThread(
{ doExpensiveCalculation() },
{ onResultLoaded(it) }
)

В этом коде передается лямбда-выражение для backgroundFunc, которая выполняет высокозатратный расчет, и лямбда-выражение для callback, обрабатывающей результат расчета.

Обработка разрешений

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

Например, можно определить функцию withPermissions, которая принимает список разрешений для запроса и функцию обратного вызова для выполнения, когда разрешения будут предоставлены. Эта функция будет выполнять проверку и запрос разрешений и вызывать функцию обратного вызова, только когда разрешения будут предоставлены.

Вот пример реализации на языке Kotlin:

fun Activity.withPermissions(vararg permissions: String, callback: () -> Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val ungrantedPermissions = permissions.filter {
checkSelfPermission(it) == PackageManager.PERMISSION_DENIED
}
if (ungrantedPermissions.isEmpty()) {
// Все разрешения предоставлены, выполнить обратный вызов
callback()
} else {
// Запрос разрешений
requestPermissions(ungrantedPermissions.toTypedArray(), 0)
}
} else {
// Устройства до версии Marshmallow, выполнение обратного вызова
callback()
}
}

Эту функцию можно использовать следующим образом:

withPermissions(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) {
// Код для выполнения при получении разрешений
}

В этом примере определяется функция withPermissions, которая принимает в качестве параметров переменное количество разрешений и функцию обратного вызова. Внутри функции проверяется, работает ли устройство на Marshmallow или более поздней версии Android, и если да, то фильтруется список разрешений, чтобы включить только те, которые еще не были предоставлены.

Если все разрешения уже предоставлены, немедленно выполняем функцию обратного вызова. В противном случае запрашиваем непредоставленные разрешения с помощью метода requestPermissions. Если же на устройстве установлена версия Android до Marshmallow, сразу же выполняем функцию обратного вызова без проверки разрешений.

Валидация ввода

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

fun String.validate(validationFunc: (String) -> Boolean): Boolean {
return validationFunc(this)
}

В этом примере определяется функция расширения validate для класса String. Функция validate принимает один параметр, который является функцией валидации, принимающей ввод String и возвращающей значение Boolean, указывающее, является ли ввод действительным или нет. Функция validate применяет функцию валидации к объекту String, для которого она вызвана, и возвращает результат.

Вот пример использования:

val input = "example input"
val isInputValid = input.validate { input -> input.isNotEmpty() }

В этом примере создаем объект String под названием input, а затем вызываем для него функцию validate, передавая лямбду, которая проверяет, не пуста ли строка. Функция validate возвращает true, если входные данные действительны, и false в противном случае.

Делегирование

Делегирование  —  это техника в объектно-ориентированном программировании, когда объект передает часть своих обязанностей другому объекту. В Kotlin делегирование реализуется с помощью ключевого слова by.

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

Вот пример:

fun <T> lazyDelegate(initializer: () -> T) = lazy(initializer)::getValue

class Example {
val lazyProperty: String by lazyDelegate {
println("Initializing lazy property")
"Hello, World!"
}
}

fun main() {
val example = Example()
println(example.lazyProperty)
println(example.lazyProperty)
}

В этом примере функция lazyDelegate принимает лямбда-функцию, которая возвращает начальное значение свойства. Она создает делегата lazy с помощью функции lazy и возвращает функцию getValue делегата. Функция getValue вызовет лямбда-функцию для инициализации свойства при первом обращении и вернет значение.

Класс Example определяет свойство lazyProperty, которое инициализируется лениво с помощью функции lazyDelegate. Лямбда-функция, переданная функции lazyDelegate, выводит сообщение в консоль и возвращает строку “Hello, World!”.

В функции main мы создаем экземпляр класса Example и дважды обращаемся к свойству lazyProperty. При первом обращении вызывается лямбда-функция для инициализации свойства, и в консоль выводится сообщение “Initializing lazy property”. При втором обращении свойство уже инициализировано, поэтому лямбда-функция не вызывается, а сразу возвращается значение “Hello, World!”.

Логирование

Логирование  —  это распространенная техника, используемая при разработке программного обеспечения для записи информации о поведении или состоянии приложения. Она может использоваться для отладки проблем, мониторинга производительности или отслеживания поведения пользователя. Во многих случаях логирование может быть реализовано как функция высшего порядка.

Вот пример логирования как функции высшего порядка в Kotlin:

fun <T> log(tag: String, message: String, function: () -> T): T {
Log.d(tag, message)
val result = function()
Log.d(tag, "Function result: $result")
return result
}

В этом примере log  —  это функция высшего порядка, которая принимает в качестве аргументов тег, сообщение и функцию. Функция выполняется, и ее возвращаемое значение логируется вместе с сообщением и тегом.

Вот пример использования этой функции:

val result = log("myTag", "Calculating result...") {
// Выполнение высокозатратного расчета
42
}

В этом примере функция log используется для логирования сообщения до и после выполнения высокозатратного вычисления. Результат вычисления затем возвращается и хранится в переменной result.

База данных Room

База данных Room  —  это популярная библиотека для работы с базами данных SQLite в Android. Room предоставляет набор аннотаций и классов, которые помогают определить базу данных и взаимодействовать с ней. Функции высшего порядка упрощают работу с Room.

Вот пример функции высшего порядка, которая принимает объект Room DAO и лямбда-функцию, выполняющую запрос к базе данных:

fun <T> runInTransaction(dao: MyDao, action: () -> T): T {
return dao.runInTransaction {
action()
}
}

В этом примере функция runInTransaction принимает объект MyDao и лямбда-функцию, выполняющую операцию с базой данных. Функция runInTransaction вызывает метод runInTransaction в объекте MyDao, который выполняет лямбда-функцию внутри транзакции базы данных. Лямбда-функция может возвращать значение, которое возвращается функцией runInTransaction.

Вот пример того, как можно использовать функцию runInTransaction для выполнения запроса к базе данных:

val dao = MyDatabase.getInstance(context).myDao()
val result = runInTransaction(dao) {
dao.getSomeData()
}

В этом примере функция runInTransaction вызывается с объектом MyDao и лямбда-функцией, которая вызывает метод getSomeData для объекта MyDao. Функция runInTransaction обрабатывает транзакцию базы данных, разбирается с ошибками и возвращает результат метода getSomeData.

Обработка toast

Toast  —  это распространенный способ отображения коротких уведомлений и обратной связи с пользователем в приложениях Android. Вот пример того, как обрабатывать toast с помощью функций высшего порядка:

fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}

fun Fragment.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
activity?.showToast(message, duration)
}

В этом примере определяется функция расширения под названием showToast как для Context, так и для Fragment. Функция принимает строку message и целое число duration, которое определяет, как долго должен отображаться toast.

Функция showToast создает новый Toast с помощью метода makeText, а затем отображает его с помощью метода show. Метод makeText принимает Context или Activity, где должен быть отображен toast, строку message и целое число duration.

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

showToast("Hello, world!")

Можно также настроить продолжительность toast:

showToast("Hello, world!", Toast.LENGTH_LONG)

Заключение

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

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

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

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Summit Kumar: 10 Practical Examples of Higher-Order Functions in Android Development

Предыдущая статья4 пользовательских хука React, которые должен знать каждый разработчик
Следующая статьяPython и Java: комплексное сравнение двух популярных языков программирования