- Делегаты в Kotlin для Android
- Основы
- Аргументы фрагмента
- Делегаты SharedPreferences
- Делегаты представления
- Заключение
- What is Android delegate?
- Android preferences delegate
- Что такое делегат и как его готовят
- Применяем делегат
- Generic
- Кастомная ошибка
- Заключение
- Fragment View Binding initialisation using Delegates
- What are Delegates?
- Property Delegation
- A Custom Property Delegate for Fragment View Binding
Делегаты в Kotlin для Android
Dec 14, 2019 · 9 min read
Kotlin действительно красивый язык, в котором есть очень крутые фичи. Из-за них разработка приложений становится веселым и захватывающим опытом. Одна из этих фич — делегированные свойства. Расскажу вам, как делегирование помогает упростить жизнь андроид-разработчику.
Основы
Пляшем от начала: что такое делегат и как он работает? На самом деле, все не так сложно, как кажется
Делегат — это всего лишь класс, который передает значение свойству и обрабатывает его изменения. Благодаря этому мы можем перемещать или делегировать логику геттера и сеттера из самого свойства в отдельный класс и переиспользовать логику.
Допустим, нам нужно свойство param типа String , всегда содержащее обрезанную строку, то есть без начального и конечного пробелов. Мы можем сделать это в сеттере свойства так:
Если вам нужна помощь по синтаксису, перейдите на страницу Свойства в документации по Kotlin.
Ну а теперь, что если нам нужно переиспользовать эту функциональность в каком-то другом классе? Вот тут-то делегаты и вступают в игру:
Итак, делегат — это класс с двумя методами: для получения и присвоения значения свойству. Чтобы у него было больше информации, он получает свойство, которое работает через инстанс класса KProperty и объект, у которого есть это свойство через thisRef . Это все! И вот как мы можем использовать этот свежесозданный делегат:
Что аналогично следующему:
::param является оператором, который возвращает инстанс класса KProperty свойству.
Очевидно, ничего сверхтаинственного в этих делегатах нет. Но несмотря на их простоту, они часто пригождаются. Рассмотрим некоторые примеры, характерные для Android.
Почитать о делегатах побольше вы всегда можете в официальной документации.
Аргументы фрагмента
Часто нам нужно передать некоторые параметры фрагменту. Обычно это выглядит так:
Так мы передаем параметры путем создания фрагмента через его статический метод newInstance . Внутри мы помещаем параметры в аргументы фрагмента, чтобы извлечь их позже через onCreate .
В наших силах сделать код немного красивее, перемещая связанную с аргументом логику в геттеры и сеттеры свойств.
Но все еще нам нужно написать примерно один и тот же код для каждого свойства. Когда их много, процесс достаточно занудный. Кроме того, выглядит код слегка беспорядочно со всей этой явной работой с аргументами.
И все же, есть ли путь для того, чтобы сделать код красивым впоследствии? Ответ положительный. И как вы уже могли догадаться, мы будем пользоваться делегированными свойствами.
Для начала подготовимся. Аргументы фрагмента хранятся в объекте Bundle , у которого есть отдельные методы для значений разных типов. Так что сделаем функцию расширения, которая пытается поместить значение случайного типа в бандл и выдает исключение, если тип не поддерживается.
Теперь мы готовы создать сам делегат:
Делегат читает значение свойства из аргументов фрагмента и, когда значение свойства меняется, он извлекает аргументы фрагмента (или создает и устанавливает новый Bundle в качестве аргументов, если у фрагмента их не было). Затем делегат записывает новое значение в эти аргументы, используя функцию расширения Bundle.put , которую мы создали ранее.
ReadWriteProperty — это дженерик-интерфейс, который принимает параметры двух типов. Первый мы сделаем Fragment , так этот делегат будет подходить только свойствам внутри фрагмента. После этого мы сможем получить доступ к инстансу фрагмента с thisRef и управлять его аргументами.
Обратите внимание, что мы используем имя свойства как ключ для аргумента, так что больше не нужно сохранять ключи как константы.
Параметр второго типа ReadWriteProperty определяет, какой вид значений может быть у свойства. Мы определенно устанавливаем тип как ненулевой и выдаем исключение, если значение невозможно прочитать. Это позволяет нам работать с ненулевыми свойствами в нашем фрагменте, уберегая процесс от раздражающих проверок на null .
Но иногда нам надо, чтобы свойство было именно обнуляемое. Так что давайте создадим другой делегат, такой, чтобы он не выдавал исключение, когда аргумент не будет найден, а возвращал вместо этого null :
А теперь давайте создадим несколько функций для удобства. Это необязательно, но исключительно ради эстетических целей, почему нет:
И наконец, начнем пользоваться нашими делегатами:
Выглядит достаточно аккуратно, да?
Делегаты SharedPreferences
Достаточно часто нам нужно сохранять некоторые значения в памяти, чтобы быстро вызвать их в следующий раз при запуске приложения. Например, мы можем захотеть сохранить какие-то пользовательские предпочтения, которые помогают им кастомизировать приложение. Общий способ сделать это — применить SharedPreferences и сохранить в них ключевое значение.
Давайте допустим, что у нас есть некоторый класс, который отвечает за сохранение и получение трех параметров:
Здесь мы получаем дефолтные SharedPreferences , и даем методы для получения и сохранения значений для наших параметров. Также мы сделали param3 , отличающийся тем, что он использует ключ специального предпочтения и имеет нестандартное значение по умолчанию.
И снова у нас есть повторяющиеся части кода. Конечно, мы можем переместить некоторые из них в приватные методы. И тем не менее у нас все равно останется достаточно громоздкий код. Кроме того, что, если мы захотим переиспользовать эту логику в каком-то другом классе? Давайте посмотрим, как делегаты могут сделать код намного чище.
На этот раз мы будем использовать выражения объектов и создадим функции расширения для класса SharedPreferences .
Здесь мы сделали функцию расширения SharedPreferences. Она возвращает объект анонимного подкласса ReadWriteProperty для нашего делегата.
Делегат читает значение свойства как String из предпочтений с помощью функции key для ключа предпочтений. По умолчанию ключ является именем свойства, так что нам не надо сохранять и передавать константы. В то же время у нас все еще есть возможность передать кастомный ключ, если, например, мы хотим обойти противоречие ключей внутри предпочтений или же захотим получить явный доступ к ключу. Также мы можем дать дефолтное значение для свойства в случае, если его нет в предпочтениях.
А еще делегат заботится о сохранении нового значения свойства в предпочтениях, применяя ту же функцию key .
Чтобы наш пример Settings работал, нам надо добавить еще два делегата для типов String? и Int , которые работают практически одинаково:
А теперь мы можем сделать наш класс Settings красивым:
И вот код выглядит намного лучше. Если нам в будущем понадобится новый параметр, его можно добавить буквально в одной строчке кода!
Делегаты представления
Предполагается, что у нас есть пользовательское представление, в котором есть три поля: заголовок, подзаголовок и описание. И все это выглядит так:
И мы хотим, чтобы наш CustomView предоставлял методы для доступа и изменения текста в этих полях:
Здесь мы применяем привязку представлений из Kotlin Android Extensions для доступа к представлениям внутри макета.
Ясно, что у нас есть некоторый код, который можно запросто переместить в отдельную сущность. Так что давайте сделаем это с помощью делегатов! Напишем функцию расширения TextView , которая возвращает делегат для работы с его текстом:
И используем его в нашем CustomView :
Убедитесь, что свойства инициализированы после наполнения представления в блоке init , так как они должны быть ненулевыми.
Это может казаться не ахти каким улучшением относительно первоначального кода, но смысл был в том, чтобы показать силу делегатов. Кроме того, их весело писать.
Конечно, вы не ограничены TextView . Например, вот вам делегат для представления видимости ( keepBounds определяет, занимает ли все еще представление место в макете или нет, когда его не видно):
А вот делегат для прогресса в ProgressBar как плавающее от 0 до 1:
А вот как нам стоит использовать их, если у нас есть progressBar в нашем CustomView :
Очевидно, вы можете делегировать что угодно — и пусть только небо будет вам ограничением!
Заключение
И вот мы прошлись по некоторым примерам делегирования свойств в Kotlin для случаев разработки на Android. Конечно, вы вправе думать, что есть и много других способов использовать их в своем приложении. Цель была продемонстрировать, насколько сильным инструментом является делегирование свойств, а также, что можно с ним сделать.
Источник
What is Android delegate?
Read remaining answer here. Likewise, what is an example of a delegate?
del·e·gate. Use delegate in a sentence. noun. The definition of a delegate is a representative authorized to speak or act for others. An example of a delegate is a politician who speaks on behalf of a group of people.
Secondly, what is delegate in Kotlin? Feb 4, 2018 · 2 min read. Delegate properties in Kotlin are basically regular properties that delegate how they are read and written to another function (think of getters and setters). From an Android development perspective, this can be used in powerful ways.
Keeping this in consideration, what is kotlin interface?
After using Kotlin in Android development for a while, I’ve just realized the benefit of Interface in Kotlin. Fortunately, Kotlin allows Interface to have code which means a class can implement an Interface, and inherit the behavior from it. To begin with, if you come from Java, you might have an interface like this.
What is delegate Java?
Delegation is the ability to treat a method as a first-class object. A C# delegate is used where Java developers would use an interface with a single method. In this article, the use of delegates in C# is discussed, and code is presented for a Java Delegate object that can perform a similar function.
Источник
Android preferences delegate
В данной статье разобран пример создания делегата для SharedPreferences, который уменьшает boilerplate и делает использование SharedPrefernces более удобным. Те кто хочет посмотреть результат, может перейти к готовому решению
Одной из насущных задач разработки под android является сохранение между сессиями приложение каких-либо данных. Основные способы для этого: хранить на сервере или в файлах на исполняемом устройстве. Одним из самых первых способов с котором знакомиться любой начинающий разработчик на android это хранение в файле при помощи уже готового инструмента SharedPreferences.
Допустим что нам нужно записывать имя пользователя и далее его отображать где либо в приложении.
Что такое делегат и как его готовят
В двух словах это класс, который инкапсулирует установку и получение свойства. Кто хочет узнать большее официальная документация.
Что бы сделать класс делегатом необходимо реализовать интерфейс ReadOnlyProperty для val и ReadWriteProperty для var. Передаем SharedPreferences, ключ по которому будет храниться свойство и дефолтное значение через конструктор. В setValue устанавливаем значение в getValue получаем значение.
Применяем делегат
Назначение свойству делегата осуществляется по ключевому слову by. Теперь каждый раз, когда данное свойство будет запрашиваться или устанавливаться будут запускаться методы getValue и setValue созданного делегата.
Так же аналогично теперь можно поступить с другими полями, к примеру, если нужно так же сохранять телефон пользователя.
Generic
Чтобы не делать для каждого типа данных отдельный делегат воспользуемся обобщениями generic официальная документация.
Обычно первое не осознанное знакомство с generic происходит при создании экземпляра класса List. Для него определяется конкретный тип данных с которым он работает.
Чтобы задать обобщенный тип данных у класса после его название необходимо указать название этой переменной в угловых скобках.
Теперь необходимо задать, что устанавливаемое значение и дефолтное значение имеют тип TValue.
Соответственно теперь создание экземпляра класса выглядит так:
Осталось сделать маппинг получения и установки свойств, определяем тип данных по delaultValue, заодно это дает smart cast этого значения к конкретному типу данных, если что то пошло не так и свойство не типа TValue возвращаем defValue.
С остальными типами данных аналогично.
Кастомная ошибка
Остается вопрос, что делать с веткой else, поскольку тип TValue может быть абсолютно любым.
Хорошим тоном будет сделать свою кастомную ошибку. Если произойдет исключение, тогда будет максимально понятно, что произошло.
Заключение
Итого получаем готовый к применению делегат:
Источник
Fragment View Binding initialisation using Delegates
View Binding in Android, allows us to seamlessly interact with the user interface by providing auto-generated classes for each XML layout (on a module by module basis).
Therefore, a routine task of setting text in a TextView can be transformed from this:
Of course, view binding requires some additional boilerplate code before we can use it. In the example above, we need to first initialise the binding .
Doing this for each fragment in our project is cumbersome. So, can we do better? Is there a cleaner, more efficient way that decreases our boilerplate code? Yes! By leveraging the power of Delegates.
The article assumes you have already set up view binding in your project. If not, refer this link to learn how to set it up.
What are Delegates?
Delegation is defined as the shifting of responsibility for some particular work from one person to another. Simply put, you can think of delegation as a process in which one object delegates (assigns/gives) a particular task to a helper object, called the delegate. It then becomes the responsibility of the delegate to execute the task and provide the result to our initial object. Kotlin inherently provides support for both class and property delegates. Since our binding object is a Fragment property, we’ll be working with property delegates.
Property Delegation
With property delegation, our delegate is responsible for handling the get and set for the specified property. We can thus abstract our common logic behind the delegate and re-use it for similar objects which in our case is the set of classes, auto-generated by view binding.
A thorough, more descriptive explanation of delegates can be found here.
A Custom Property Delegate for Fragment View Binding
Without further ado, let’s start writing our view binding delegate.
Step 1: Create the delegate class
Create a new class FragmentViewBindingDelegate.kt and implement the ReadOnlyProperty interface. In case your property is mutable, you can use the ReadWriteProperty interface but since our binding is immutable, we only extend the read only interface. In addition, we require the binding class in our delegate constructor.
Step 2: Implement the ReadOnlyProperty interface
As soon as we implement the ReadOnlyProperty interface, we are asked to implement the getValue member function.
The first param of getValue() is thisRef which represents the object that holds our property. It is of type Fragment in our case. The type for thisRef is governed by how we implement our interface. This is why we implemented ReadOnlyProperty instead of ReadOnlyProperty .
Step 3: Implement getValue
getValue() needs to return an instance of our binding object. So we create a variable called binding . In getValue() we initialise binding if it’s not initialised and return it. This ensures we only initialise binding once and subsequently re-use the same instance.
At this point, we have handled most of our boilerplate code and can initialise our binding as:
However, there are a few problems
1. The syntax to initialise binding can be improved
2. Our delegate is not lifecycle aware and thus, it will leak memory
Let’s resolve them. Read on.
Step 4: Clean our binding init syntax
Our delegate requires the class of our generic Class to return the correct view binding instance. Another way to get this information is by using the reified keyword in Kotlin. You can learn more about reified here.
Add this inline function outside your delegate class:
Et voilà! You can now initialise your binding with a clean, concise syntax.
Step 5: Make FragmentViewBindingDelegate lifecycle aware
To make our delegate lifecycle aware we need to pass an instance of our host fragment to it.
And, update our inline function.
Finally, subscribe to the fragment’s lifecycle and set the binding to null once the fragment is destroyed ensuring our delegate does not leak any memory.
Источник