Найти в Дзене
Nuances of programming

Делегаты в Kotlin для Android

Оглавление

Источник: Nuances of Programming

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

Основы

Пляшем от начала: что такое делегат и как он работает? На самом деле, все не так сложно, как кажется

Делегат — это всего лишь класс, который передает значение свойству и обрабатывает его изменения. Благодаря этому мы можем перемещать или делегировать логику геттера и сеттера из самого свойства в отдельный класс и переиспользовать логику.

Допустим, нам нужно свойство param типа String, всегда содержащее обрезанную строку, то есть без начального и конечного пробелов. Мы можем сделать это в сеттере свойства так:

-2

Если вам нужна помощь по синтаксису, перейдите на страницу Свойства в документации по Kotlin. 

Ну а теперь, что если нам нужно переиспользовать эту функциональность в каком-то другом классе? Вот тут-то делегаты и вступают в игру:

-3

Итак, делегат — это класс с двумя методами: для получения и присвоения значения свойству. Чтобы у него было больше информации, он получает свойство, которое работает через инстанс класса KPropertyи объект, у которого есть это свойство через thisRef. Это все! И вот как мы можем использовать этот свежесозданный делегат:

-4

::param является оператором, который возвращает инстанс класса KProperty свойству.

Очевидно, ничего сверхтаинственного в этих делегатах нет. Но несмотря на их простоту, они часто пригождаются. Рассмотрим некоторые примеры, характерные для Android.

Почитать о делегатах побольше вы всегда можете в официальной документации.

Аргументы фрагмента

Часто нам нужно передать некоторые параметры фрагменту. Обычно это выглядит так:

-5

Так мы передаем параметры путем создания фрагмента через его статический метод newInstance. Внутри мы помещаем параметры в аргументы фрагмента, чтобы извлечь их позже через onCreate.

В наших силах сделать код немного красивее, перемещая связанную с аргументом логику в геттеры и сеттеры свойств.

-6

Но все еще нам нужно написать примерно один и тот же код для каждого свойства. Когда их много, процесс достаточно занудный. Кроме того, выглядит код слегка беспорядочно со всей этой явной работой с аргументами.

И все же, есть ли путь для того, чтобы сделать код красивым впоследствии? Ответ положительный. И как вы уже могли догадаться, мы будем пользоваться делегированными свойствами.

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

-7

Теперь мы готовы создать сам делегат:

-8

Делегат читает значение свойства из аргументов фрагмента и, когда значение свойства меняется, он извлекает аргументы фрагмента (или создает и устанавливает новый Bundle в качестве аргументов, если у фрагмента их не было). Затем делегат записывает новое значение в эти аргументы, используя функцию расширения Bundle.put, которую мы создали ранее.

ReadWriteProperty— это дженерик-интерфейс, который принимает параметры двух типов. Первый мы сделаем Fragment, так этот делегат будет подходить только свойствам внутри фрагмента. После этого мы сможем получить доступ к инстансу фрагмента с thisRefи управлять его аргументами.

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

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

Но иногда нам надо, чтобы свойство было именно обнуляемое. Так что давайте создадим другой делегат, такой, чтобы он не выдавал исключение, когда аргумент не будет найден, а возвращал вместо этого null:

-9

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

-10

Выглядит достаточно аккуратно, да?

Делегаты SharedPreferences

Достаточно часто нам нужно сохранять некоторые значения в памяти, чтобы быстро вызвать их в следующий раз при запуске приложения. Например, мы можем захотеть сохранить какие-то пользовательские предпочтения, которые помогают им кастомизировать приложение. Общий способ сделать это — применить SharedPreferences и сохранить в них ключевое значение.

Давайте допустим, что у нас есть некоторый класс, который отвечает за сохранение и получение трех параметров:

-11

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

И снова у нас есть повторяющиеся части кода. Конечно, мы можем переместить некоторые из них в приватные методы. И тем не менее у нас все равно останется достаточно громоздкий код. Кроме того, что, если мы захотим переиспользовать эту логику в каком-то другом классе? Давайте посмотрим, как делегаты могут сделать код намного чище. 

На этот раз мы будем использовать выражения объектов и создадим функции расширения для класса SharedPreferences.

-12

Здесь мы сделали функцию расширения SharedPreferences. Она возвращает объект анонимного подкласса ReadWriteProperty для нашего делегата.

Делегат читает значение свойства как String из предпочтений с помощью функции key для ключа предпочтений. По умолчанию ключ является именем свойства, так что нам не надо сохранять и передавать константы. В то же время у нас все еще есть возможность передать кастомный ключ, если, например, мы хотим обойти противоречие ключей внутри предпочтений или же захотим получить явный доступ к ключу. Также мы можем дать дефолтное значение для свойства в случае, если его нет в предпочтениях. 

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

Чтобы наш пример Settings работал, нам надо добавить еще два делегата для типов String?и Int, которые работают практически одинаково:

-13

А теперь мы можем сделать наш класс Settings красивым:

-14

И вот код выглядит намного лучше. Если нам в будущем понадобится новый параметр, его можно добавить буквально в одной строчке кода!

Делегаты представления

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

-15

И мы хотим, чтобы наш CustomView предоставлял методы для доступа и изменения текста в этих полях:

-16

Здесь мы применяем привязку представлений из Kotlin Android Extensions для доступа к представлениям внутри макета.

Ясно, что у нас есть некоторый код, который можно запросто переместить в отдельную сущность. Так что давайте сделаем это с помощью делегатов! Напишем функцию расширения TextView, которая возвращает делегат для работы с его текстом:

-17

Убедитесь, что свойства инициализированы после наполнения представления в блоке init, так как они должны быть ненулевыми. 

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

Конечно, вы не ограничены TextView. Например, вот вам делегат для представления видимости (keepBounds определяет, занимает ли все еще представление место в макете или нет, когда его не видно):

-18

А вот как нам стоит использовать их, если у нас есть progressBar в нашем CustomView:

-19

Очевидно, вы можете делегировать что угодно — и пусть только небо будет вам ограничением!

Заключение

И вот мы прошлись по некоторым примерам делегирования свойств в Kotlin для случаев разработки на Android. Конечно, вы вправе думать, что есть и много других способов использовать их в своем приложении. Цель была продемонстрировать, насколько сильным инструментом является делегирование свойств, а также, что можно с ним сделать.

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

Читайте нас в телеграмме и vk

Перевод статьи Dmitry Akishin: Kotlin Delegates in Android: Utilizing the power of Delegated Properties in Android development