Kotlinx android parcel parcelize

Easy Parcelable in Kotlin

Parcelable

If you are an Andorid developer and ever tried to pass your custom Model or Data class from one Activity to another Activity, you must encountered with Parcelables.

Basically Parcelable is a component of Android SDK, an Interface which is similar to JAVA Serializable. It is Android’s recommended way of passing your custom data structure between different components in your app, because Parcelable is processed relatively fast compared to the standard JAVA serialization as you need to be explicit about the serialization process instead of using reflection to infer it.

How to make my class Parcelable

Making your class Parcelable is always painful, no matter Kotlin or JAVA whatever language you use. So developer’s started to make different Android Studio plugins and libraries to make your life easier.

There are 3 ways you can make your class Parcelable:

  1. Implementing the Parcelable interface and defining the serialization yourself (The traditional way)
  2. Using Android Studio plugins, like Android Parcelable code generator
  3. Using annotation-based libraries, like Parceler

The example

I will be using Kotlin for our examples. Let’s say we are making an online marketplace app and we have a custom Data class called Item like following:

Parcelable: The traditional way

Parcelable: The lazy coder’s way

See, easy as that! You just to confirm followings:

  • Use @Parcelize annotation on top of your Model / Data class
  • Use latest version of Kotlin (v1.1.51 at the time of writing this article)
  • Use latest version of Kotlin Android Extensions in your app module, so your build.gradle may look like:

Passing Complex Object as Parcelable

Let’s assume your Item is a complex object, which has another data structure Category consisting categoryId and categoryName as below:

You can simply use it within your Item Parcelable by making itself a Parcelable class like below:

So, your final Item class may look like this:

All you need to do is, define your nested class as another Parcelable class — that’s it!

Well, passing and retrieving the Parcelable object is even easier:

Passing the Parcelable

Retrieving the Parcelable

That’s all for now. Stay Lazy and Productive.

Источник

Пишем плагин Parcelize для компилятора Kotlin под iOS

В этой статье описан мой опыт по написанию плагина для компилятора Kotlin. Моей главной целью было создание плагина под iOS (Kotlin/Native), аналогичного kotlin-parcelize под Android. Дело в том, что в iOS, как и в Android, приложения тоже могут быть убиты системой, а значит, может возникнуть необходимость сохранять стек навигации и другие данные. В результате работы над этой задачей получился kotlin-parcelize-darwin. Подробности о его создании и применении — под катом.

Читайте также:  Максимальная версия андроида 2021

Parcelize в Android

Хотя в статье будет описана разработка под iOS, давайте вспомним, что собой представляют интерфейс Parcelable и плагин kotlin-parcelize под Android. Интерфейс Parcelable позволяет нам сериализовать в Parcel реализующий класс, чтобы он был представлен в виде массива байтов. Также он позволяет десериализовать класс из Parcel для восстановления всех данных. Эта возможность широко используется для записи и восстановления состояний экрана, например когда приостановленное приложение сначала убивается системой из-за нехватки памяти, а затем снова активируется.

Реализовать интерфейс Parcelable не сложно. Нужно написать два основных метода:

writeToParcel(Parcel, …) — пишет данные в Parcel,

createFromParcel(Parcel) — читает из Parcel.

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

К счастью, для компилятора Kotlin есть плагин kotlin-parcelize. Если его включить, то достаточно лишь пометить класс Parcelable с помощью аннотации @Parcelize — и плагин автоматически сгенерирует реализацию. Это избавляет нас от написания соответствующего шаблонного кода и гарантирует корректность реализации на этапе компилирования.

Применение Parcelize в iOS

Поскольку iOS-приложения тоже имеют вышеупомянутые особенности, есть аналогичные способы сохранения состояния приложения. Один из них заключается в использовании протокола NSCoding, который очень похож на интерфейс Parcelable в Android. Классы тоже должны реализовать два метода:

encode(with: NSCoder)— кодирует объект в NSCoder,

init?(coder: NSCoder)— декодирует объект из NSCoder.

Kotlin Native под iOS

Kotlin не ограничен Android — его можно использовать для написания Kotlin Native-фреймворков под iOS и даже мультиплатформенного кода. А поскольку iOS-приложения тоже могут быть убиты системой, то с ними возникают те же проблемы. Kotlin Native под iOS предлагает двунаправленную совместимость с Objective-C, то есть мы можем использовать NSCoding и NSCoder.

Очень простой класс данных может выглядеть так:

Добавим реализацию протокола NSCoding:

Выглядит просто. Попробуем скомпилировать:

Попробуем расширить класс NSObject с помощью класса данных User:

И опять не компилируется!

Интересно. Похоже, компилятор пытается переопределить и сгенерировать метод toString, но для классов, наследующихся от NSObject, нам нужно переопределить метод description. Кроме того, нам, вероятно, вообще не стоит использовать наследование, потому что это может помешать пользователям расширять их собственные классы Kotlin (из-за невозможности множественного наследования).

Parcelable для iOS

Нам нужно другое решение, без использования наследования. Определим интерфейс Parcelable:

Всё просто. Классы Parcelable будут иметь только метод coding, который возвращает экземпляр NSCodingProtocol. Остальное будет обработано реализацией протокола.

Теперь давайте изменим класс User таким образом, чтобы он реализовал интерфейс Parcelable:

Мы создали вложенный класс CodingImpl, который реализует протокол NSCoding. Метод encodeWithCoder остался неизменным, а вот с initWithCoder ситуация чуть сложнее. Он должен возвращать экземпляр протокола NSCoding, но класс User теперь им не является. Нам нужно какое-то обходное решение, промежуточный класс:

Класс DecodedValue реализует протокол NSCoding и хранит некоторый объект value. Все методы могут быть пустыми, потому что этот класс не будет ни кодироваться, ни декодироваться. Теперь мы можем использовать его в методе initWithCoder класса User:

Читайте также:  Андроид пишет ошибку подключения вай фай

Тестирование

Давайте напишем тест, чтобы проверить, всё ли работает правильно.

Создаём экземпляр класса User с какими-нибудь данными.

Кодируем его с помощью NSKeyedArchiver, в качестве результата получаем NSData.

Декодируем NSData с помощью NSKeyedUnarchiver.

Убеждаемся, что декодированный объект аналогичен исходному.

Пишем плагин для компилятора

Мы определили интерфейс Parcelable под iOS, попробовали его в работе с помощью класса User и протестировали код. Теперь можно автоматизировать реализацию Parcelable, чтобы код генерировался автоматически, как при использовании kotlin-parcelize под Android.

Мы не можем использовать Kotlin Symbol Processing (KSP), потому что это не позволит нам менять существующие классы, а только генерировать новые. Так что единственным решением будет написать плагин для компилятора Kotlin. Но это не так просто, в основном потому, что документации до сих пор нет, API работает нестабильно и т. д. Если вы всё же соберётесь писать плагин для компилятора, то рекомендую обратиться к этим источникам:

Плагин работает так же, как kotlin-parcelize. Классы должны реализовывать интерфейс Parcelable и быть помечены с помощью аннотации @Parcelize. При компилировании плагин генерирует реализации Parcelable. Классы Parcelable выглядят так:

Название плагина

Плагин называется kotlin-parcelize-darwin. Часть “-darwin” означает, что плагин должен работать со всеми платформами Darwin (Apple), но сейчас нас интересует только iOS.

Gradle-модули

Kotlin-parcelize-darwin — первый модуль, который нам понадобится. Он содержит плагин для Gradle, который регистрирует плагин для компилятора, и ссылается на два артефакта: один — для плагина компилятора Kotlin/Native, второй — для плагина компилятора под все другие платформы.

kotlin-parcelize-darwin-compiler — модуль плагина для компилятора Kotlin/Native.

kotlin-parcelize-darwin-compiler-j — модуль плагина для ненативного компилятора. Он необходим, потому что является обязательным и на него ссылается Gradle-плагин. Хотя на самом деле этот модуль пустой, поскольку нам ничего не нужно из ненативного варианта.

kotlin-parcelize-darwin-runtime — содержит зависимости времени выполнения (runtime) для компиляторного плагина. Например, здесь находятся интерфейс Parcelable и аннотация @Parcelize.

tests — содержит тесты для компиляторного плагина, добавляет в плагин модули в виде included builds.

Процесс установки плагина

В корневом файле build.gradle:

В файле build.gradle проекта:

Реализация

Генерирование кода в Parcelable состоит из двух основных этапов. Нам нужно:

Сделать код компилируемым с помощью добавления синтетических заглушек для отсутствующих методов fun coding(): NSCodingProtocol из интерфейса Parcelable.

Сгенерировать реализации для заглушек, добавленных на предыдущем этапе.

Добавление заглушек

Это делается с помощью класса ParcelizeResolveExtension, который реализует интерфейс SyntheticResolveExtension. Всё очень просто: класс реализует методы getSyntheticFunctionNames и generateSyntheticMethods, которые вызываются при компилировании для каждого класса.

Как видите, сначала нужно проверить, можно ли применять логику Parcelize к текущему классу. Для этого используется функция isValidForParcelize:

Мы обрабатываем только те классы, у которых есть аннотация @Parcelize и которые реализуют интерфейс Parcelable.

Генерирование реализаций заглушек

Как вы могли догадаться, этот этап создания плагина значительно сложнее. За него отвечает класс ParcelizeGenerationExtension, который реализует интерфейс IrGenerationExtension. Он содержит всего один метод:

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

Читайте также:  Гонки внедорожников для андроид

Проходить по классам не сложно:

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

Итак, сначала нам снова нужно проверить, можно ли применять логику Parcelize к текущему классу (irClass):

Затем добавим в irClass вложенный класс CodingImpl, определим его супертипы (NSObject и NSCoding) и пометим аннотацией @ExportObjCClass (чтобы класс был доступен при поиске во время выполнения):

Теперь добавим в класс CodingImpl первичный конструктор. У него должен быть только один аргумент — data: TheClass, поэтому нам также надо сгенерировать поле (field) data, свойство (property) и метод считывания (getter).

Вот что у нас получается:

Добавим реализацию протокола NSCoding:

Теперь сгенерированный класс выглядит так:

Наконец нам нужно сгенерировать тело метода coding(), просто создав экземпляр класса CodingImpl:

Использование плагина

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

Вот обобщённый пример использования Parcelable в Kotlin, который демонстрирует, как можно сохранить и восстановить данные:

А вот пример того, как мы можем кодировать и декодировать классы Parcelable в iOS-приложении:

Parcelize в Kotlin Multiplatform

Теперь у нас есть два плагина: kotlin-parcelize для Android и kotlin-parcelize-darwin — для iOS. И мы можем применить их оба и использовать @Parcelize в общем коде!

Файл build.gradle нашего общего модуля будет выглядеть так:

Теперь у нас в наборах androidMain и iosMain есть доступ к интерфейсам Parcelable и аннотациям @Parcelize. Чтобы использовать их в commonMain, нам нужно вручную определить их с помощью expect/actual.

Напишем в commonMain:

Во всех остальных наборах:

Теперь мы можем использовать Parcelize как обычно в commonMain. При сборке под Android код будет обработан плагином kotlin-parcelize, а при сборке под iOS — плагином kotlin-parcelize-darwin. В случае с другими платформами ничего не будет сделано, потому что интерфейс Parcelable будет пуст, а аннотация будет отсутствовать.

Заключение

Мы рассмотрели компиляторный плагин kotlin-parcelize-darwin. Исследовали его структуру и принцип работы, узнали, как его можно применять в Kotlin Native, как подружить его с Android-плагином kotlin-parcelize в Kotlin Multiplatform, а также как использовать Parcelable на стороне iOS.

Исходный код лежит на GitHub. Плагин ещё не опубликован, но вы уже можете с ним экспериментировать, опубликовав в локальном Maven-репозитории или с помощью Gradle Composite builds.

В репозитории лежит очень простой пример проекта, в котором есть общий модуль, а также Android- и iOS-приложения. Спасибо, что дочитали, и не забудьте подписаться на меня в Twitter!

Источник

Оцените статью