Отличная работа по завершению первых двух разделов книги! Ты отлично справляешься. Теперь, когда вы знакомы с Kotlin Multiplatform, у вас есть все необходимое для решения задач последнего раздела.
Здесь вы начнете создавать новое приложение под названием Learn . Он построен на основе понятий, изученных вами в предыдущих главах, а также вводит новый набор понятий: сериализацию, работу в сети и способы обработки параллелизма.
Learn использует RSS-каналы raywenderlich.com, чтобы показывать последние учебные пособия для Android, iOS, Unity и Flutter. Вы можете добавить их в список для чтения позже, поделиться ими с другом, выполнить поиск определенного ключа или просто просмотреть все, что выпустила команда. Он будет иметь тот же внешний вид, к которому вы уже привыкли на веб-сайте raywenderlich.com.
Необходимость сериализации
Ваше приложение может отправлять или получать данные от третьей стороны, будь то удаленный сервер или другое приложение на устройстве. Сериализация — это процесс преобразования данных в правильный формат перед отправкой, а десериализация — это процесс их преобразования обратно в конкретный объект после их получения.
Существуют разные типы форматов сериализации — например, JSON или потоки байтов. Вы прочтете об этом больше в Главе 12, «Сеть», когда будете рассматривать сетевые запросы.
Android использует эту концепцию для обмена данными между действиями, службами или получателями — либо в том же приложении, либо в сторонних приложениях. Разница в том, что вместо того, чтобы полагаться на Serializable
отправку данных из пользовательских типов, ОС требует, чтобы вы реализовали Parcelable
отправку этих объектов.
Обзор проекта
Чтобы следовать примерам кода в этом разделе, загрузите начальный проект и откройте 11-serialization/projects/starter с помощью Android Studio.
Примечание . В этом проекте используется Jetpack Compose 1.2.0-alpha01 и Multiplatform Compose 1.1.0 с Kotlin 1.6.10. Для успешной сборки и запуска приложения используйте Android Studio Bumblebee 2021.1.1 Patch 2 или более новую версию.
В Starter есть скелет приложения, которое вы создадите, а Final дает вам что-то, с чем можно сравнить ваш код, когда вы закончите.
После синхронизации проекта вы увидите набор подпапок и других важных файлов:

Рис. 11.1 – Иерархия представления проекта
В следующем разделе подробно объясняется иерархия дерева проекта. Имена папок говорят сами за себя и соответствуют либо платформе, для которой они используются, либо самой функциональности. Вы можете пропустить его и сразу перейти к разделу возможностей приложения .
Android-приложение
Приложение Android, расположенное внутри androidApp , содержит файлы конфигурации Gradle, исходный код приложения и его ресурсы. Это та же структура, к которой вы уже привыкли в своих приложениях для Android, и вы можете использовать любую библиотеку или компонент, как обычно:
- компоненты : общие составные функции, которые представляют определенную цель и используются на разных экранах.
- ui : содержит весь пользовательский интерфейс. Подпакеты соответствуют определенным экранам ( закладки , домашний , последний или поиск ), панели навигации ( основной ), системе разработки приложений ( тема ) или служебным классам (утилита ) .
- RWApplication.kt : класс Application , который инициализирует контекст , необходимый SQLDelight .
Создайте и запустите приложение.

Рис. 11.2 — Работа стартового проекта Android. Пустой экран без данных.
Это скелет вашего приложения. Выглядит не очень, но это потому, что нет данных для показа. Вы загрузите данные приложения в следующих разделах.
iOS-приложение
Ваше приложение для iOS находится в папке iOSApp . Перейдите в эту папку и в корневой каталог и введите:
pod install
Это позволяет получить все зависимости проекта.
После этого пришло время открыть проект. Используйте Xcode или AppCode, чтобы открыть файл iosApp.xcworkspace , расположенный в папке iosApp .
Примечание . Существует два разных файла iosApp.* : xcworkspace , созданный CocoaPods, и xcodeproj , созданный Xcode. Вам нужно открыть первый, чтобы IDE могла правильно разрешить зависимости проекта.
Вам не нужно никаких дополнительных шагов для запуска приложения. Откройте цель iosApp , перейдите на вкладку « Фазы сборки » и выберите раскрывающийся список « Выполнить сценарий» . Здесь у вас есть:
cd "$SRCROOT/.." ./gradlew :shared:embedAndSignAppleFrameworkForXcode
Эта задача автоматически компилирует платформу SharedKit и при необходимости добавляет ее в ваш проект.
После синхронизации проекта скомпилируйте и запустите приложение. Вы увидите такой экран:

Рис. 11.3 — Работа стартового проекта iOS. Пустой экран без данных.
Настольное приложение
Настольное приложение похоже на приложение для Android. Код был скопирован из одного проекта в другой с небольшими изменениями, а именно в используемых библиотеках, которые не были доступны для цели JVM :
- Kamel : библиотека загрузки изображений.
- precompose : библиотека сообщества, позволяющая использовать Jetpack Lifecycle , ViewModel , LiveData и Navigation в настольном приложении.
Хотя эти библиотеки доступны по указанным выше ссылкам, они не обновлены до последних версий Kotlin или Compose. Вот почему они включены в этот проект. Дополнительную информацию об этом можно найти в Приложении C.
Чтобы запустить настольное приложение, перейдите в командную строку и в корневой папке проекта введите:
./gradlew desktopApp:run
После его завершения откроется новое окно с приложением. Вы увидите такой экран:

Рис. 11.4 — Работающий стартовый проект рабочего стола. Пустой экран без данных.
Примечание . Используйте мышь для изменения размера окна.
Общий модуль
Это содержит всю бизнес-логику Learn . Это многоплатформенный код, который используется на Android, iOS и настольных компьютерах.
Ты найдешь:
- androidMain : определение кода для платформы Android.
- androidTest : тесты, написанные для упомянутого выше кода для конкретной платформы.
- commonMain : содержит бизнес-логику, которая будет использоваться на всех целевых платформах.
- commonTest : содержит все тесты, не зависящие от платформы, созданные для проверки того, работает ли бизнес-логика должным образом.
- iosMain : определение кода для платформы iOS.
- iostTest : тесты, написанные для упомянутого выше кода для конкретной платформы.
Общий код
Когда вы откроете общий модуль, вы увидите две вещи внутри commonMain :
- kotlin : Где находится бизнес-логика приложения.
- sqldelight : содержит файл SQL, который будет использоваться SQLDelight для создания базы данных приложения, а также соответствующие методы CRUD для взаимодействия с ней.
Этот модуль следует парадигме чистой архитектуры, группируя файлы в соответствии с их ответственностью в бизнес-логике. Откройте каталог kotlin , и вы увидите:
- data : сетевой уровень общего модуля. Извлекает каналы RW и определяет модель данных для каждой записи RSS.
- domain : десериализует ответ и создает список каналов, которые позже могут быть использованы пользовательским интерфейсом. Сохраняет данные в базу данных и определяет обратные вызовы, которые будут использоваться для уведомления о появлении новых данных.
- платформа : объявляет, какие функции необходимо реализовать на каждой платформе. Он представляет код приложения для конкретной платформы, и именно здесь вы найдете ожидаемое ключевое слово.
- презентация : этот слой создает мост между пользовательским интерфейсом и логикой приложения.
- ServiceLocator : одноэлементный объект , который обеспечивает доступ к различным модулям в приложении. Он также отвечает за инициализацию всех презентаторов вместе со свойствами, которые им требуются.
В следующем разделе вы увидите, как будет выглядеть обучение после реализации всех необходимых функций.
Возможности приложения
Прежде чем приступить к написанию кода, взгляните сначала на концепцию приложения и его функции:

Рис. 11.5 – Обзор экранов приложений и навигация.
Не беспокойтесь о деталях на каждом экране. У вас будет возможность увидеть их поближе в следующих главах.
Learn имеет четыре разных экрана, на которые можно перейти из нижней панели приложения:
Дома
Это экран приложения по умолчанию. Он показывает горизонтальный список со всеми темами raywenderlich.com и списком последних опубликованных статей.
Эти темы работают как фильтр. Щелчок по любому элементу перенаправляет пользователя на последний экран, где он может увидеть самые последние статьи, написанные, прочитанные или опубликованные, а затем добавить их в список закладок или удалить, когда они будут готовы.
Чтобы открыть один, нажмите на карточку. Вы будете автоматически перенаправлены в браузер, где сможете получить к нему доступ и прочитать его. Если вместо этого вы нажмете на три точки внутри карточки, приложение отобразит нижний лист. Оттуда вы можете добавить его в свой список для чтения позже, доступный на экране закладок , или отправить его другу, чтобы он мог быть в курсе последних статей.
Закладки
На этом экране отображаются все статьи, которые вы сохранили. Список становится большим? Выберите один и начните читать его. После этого вы можете удалить его из этого списка, щелкнув три точки на карточке и выбрав Удалить из закладок .
Самый последний
Он имеет более графический интерфейс с разделами и обложками последних статей. Вы можете прокручивать его по горизонтали, чтобы увидеть его содержимое, или по вертикали, чтобы переключаться между разными темами.
Поиск
Там много контента, который вы можете просмотреть. Здесь вы можете отфильтровать по определенному ключевому слову и, наконец, найти ту статью, которую искали.
Теперь, когда вы знакомы с приложением и проектом, пришло время научиться реализовывать эти функции.
Добавление сериализации в конфигурацию Gradle
Kotlin не поддерживает сериализацию и десериализацию непосредственно на уровне языка. Вам нужно будет либо реализовать все с нуля, либо использовать библиотеку, которая уже предоставляет вам эту поддержку. Более того, поскольку вы разрабатываете для мультиплатформы, важно помнить, что у вас не может быть Java-кода в commonMain . В противном случае проект не скомпилируется. Он должен быть написан на Kotlin для работы на разных платформах, на которые вы ориентируетесь: Android, Native и JVM.
Нет, нет необходимости реализовывать эту поддержку. Он уже доступен в kotlinx.serialization — библиотеке, созданной и поддерживаемой JetBrains.
Примечание : Подробнее о kotlinx.serialization читайте в официальной документации . Или погрузитесь в исходный код, доступный в его официальном репозитории .
Пришло время импортировать эту библиотеку в приложение. Откройте Android Studio и дождитесь завершения синхронизации проекта. В корневой папке проекта есть файл build.gradle.kts ; откройте его и добавьте следующее classpath
:
classpath("org.jetbrains.kotlin:kotlin-serialization:1.6.10")
С помощью этого объявления система сборки знает, где она должна искать и извлекать библиотеку сериализации.
Теперь откройте файл build.gradle.kts внутри общего доступа и загрузите плагин сериализации, добавив следующий код внутри plugins
блока:
kotlin("plugin.serialization")
Синхронизируйте и дождитесь завершения этого процесса. Когда все будет готово, система загрузит библиотеку и добавит ее в проект.
В проекте есть четыре разных файла build.gradle.kts :
- build.gradle.kts : находится в корневой папке проекта и настраивает как приложение Android, так и общий модуль. Он содержит репозитории, из которых будут загружены зависимости.
- shared/build.gradle.kts : это файл конфигурации общего модуля. Он содержит подключаемые модули, которые будут использоваться, библиотеки, а также платформы, на которые вы собираетесь ориентироваться.
- androidApp/build.gradle.kts : файл конфигурации для приложения Android. Определяет набор параметров, используемых для компиляции проекта, а именно версию SDK, зависимости и флаги компиляции.
- desktopApp/build.gradle.kts : это файл конфигурации для настольного приложения. Он определяет ту же конфигурацию, что и файл Android build.gradle.kts .
Различные форматы сериализации
kotlinx.serialization нестандартно поддерживает набор форматов сериализации:
- JSON : облегченный удобочитаемый формат обмена данными. Learn использует их для загрузки ссылок RSS-канала RW из локального файла, а затем для десериализации данных, полученных с сервера ( kotlinx-serialization-json ).
- Буферы протоколов : кроссплатформенный механизм для сериализованных структурированных данных ( kotlinx-serialization-protobuf ).
- CBOR : формат сериализации двоичных данных ( kotlinx-serialization-cbor ).
- Свойства : файл «ключ-значение», в котором сохраняются параметры конфигурации приложения, используемого в технологиях, связанных с Java. В разработке для Android у вас есть файл с именем gradle.properties , который содержит свойства для настройки вашего демона Gradle/вашего приложения во время компиляции ( kotlinx-serialization-properties ).
- HOCON : надмножество JSON, которое более удобочитаемо и обычно используется в файлах конфигурации ( kotlinx-serialization-hocon ).
Примечание . Существуют также две поддерживаемые сообществом библиотеки для YAML и Apache Avro .
За исключением kotlinx-serialization-json , все эти библиотеки пока экспериментальные. Хотя все они кажутся надежными и используются в ряде приложений, стоит упомянуть, что API может (значительно) измениться в будущих выпусках, а это означает, что вы можете в конечном итоге реорганизовать его использование.
В рамках этой книги вам нужно будет только добавить kotlinx-serialization-json . Перейдите к shared , откройте файл build.gradle.kts и найдите commonMain
поле. Набор уже есть dependencies
на проекте. В конце этого списка добавьте:
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
Синхронизируйте проект, и теперь вы готовы использовать сериализацию для формата JSON.
Создание пользовательского сериализатора
Если вы используете пользовательские типы для своих объектов, сериализатор может быть недоступен. Вам нужно будет создать свой собственный, иначе сериализация/десериализация не будет работать.
Хорошим примером этого является класс данных RWContent.kt внутри общего модуля. Тип platform
поля — PLATFORM
перечисление, созданное для определения того, к какому разделу относится статья.
Если вы не создадите собственный сериализатор, kotlinx-serialization-json не сможет определить ключевые слова «Все», «Android», «iOS», «Unity» или «Flutter». Это происходит потому, что атрибут в JSON имеет тип String
, а должен быть PLATFORM
вместо него. Для обеспечения этой поддержки необходимо реализовать собственный сериализатор/десериализатор.
В пакете commonMain внутри общего модуля перейдите к данным и создайте файл RWSerializer.kt .
Начните с добавления findByKey
метода:
private fun findByKey(key: String, default: PLATFORM = PLATFORM.ALL): PLATFORM { return PLATFORM.values().find { it.value == key } ?: default }
Это получит key
и вернет соответствующее значение в перечислении PLATFORM
.
Теперь, когда вы добавили функцию сопоставления, создайте RWSerializer
объект. В этот же файл добавьте:
//1 @OptIn(ExperimentalSerializationApi::class) //2 @Serializer(forClass = PLATFORM::class) //3 object RWSerializer : KSerializer<PLATFORM> { //4 override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("PLATFORM", PrimitiveKind.STRING) //5 override fun serialize(encoder: Encoder, value: PLATFORM) { encoder.encodeString(value.value) } //6 override fun deserialize(decoder: Decoder): PLATFORM { return try { val key = decoder.decodeString() findByKey(key) } catch (e: IllegalArgumentException) { PLATFORM.ALL } } }
Вот пошаговая разбивка этой логики:
- API сериализации все еще является экспериментальным. Каждый раз, когда вы его используете, Android Studio автоматически подчеркивает его вызов, предлагая либо использовать,
OptIn
либоRequire
аннотировать. Эта ошибка отображается для уведомления разработчика о том, что этот API может измениться в будущем. В этом проекте вы будете использоватьOptIn
, чтобы избежать добавления новой аннотации при каждом вызове этого класса. - Свяжите
RWSerializer
с определенным классом. В этом сценарии он соответствует enumPLATFORM
. - Вам нужно будет расширить
KSerializer
класс и определить тип объекта, который будет сериализован/десериализован. - Необходимо определить,
PrimitiveSerialDescriptior
что содержит имя класса —PLATFORM
— и как параметр должен быть прочитан. В данном случае это будет файлString
. - Теперь, когда все готово, пришло время определить
serialize
метод. Поскольку тип содержимогоPLATFORM
—String
, вам просто нужно вызватьencodeString
и отправитьvalue
полученный объект. - Наконец, для десериализации вы вызовете противоположный метод, то есть
decodeString
, и с необработанным значением (ключом) вы вызовете,findByKey
чтобы увидеть, какому значению перечисления оно соответствует.
Вот и все! RWSerializer
готов. Вам просто нужно добавить его в класс. Снова откройте файл RWContent.kt и над объявлением PLATFORM
добавьте:
@Serializable(with = RWSerializer::class)
Это связывает новый сериализатор устройства с PLATFORM
классом. Не забудьте импортировать из kotlinx.serialization.Serializable
.
Сериализация/десериализация новых данных
Перейдите к FeedPresenter.kt внутри commonMain/presentation , и вы увидите свойство RW_CONTENT . Этот JSON содержит всю необходимую информацию для создания верхнего горизонтального списка приложения на главном экране. Его структура имеет следующие атрибуты:
- платформа : различные области, охватываемые статьями raywenderlich.com: Android, iOS, Unity и Flutter. Есть пятое значение — все. После установки он удаляет этот фильтр и показывает все опубликованные.
- url : содержит URL-адрес RSS-канала, откуда следует получать статьи.
- image : изображение обложки, соответствующее
platform
выбранному.
Эти три атрибута уже сопоставлены с классом данных RWContent.kt , который находится внутри data/model . Откройте его и добавьте в начало объявления:
@Serializable
Класс RWContent
данных использует @Serializable
, поэтому при декодировании свойства RW_CONTENT он легко генерирует список RWContent , который сопоставляет атрибуты строки JSON с полями в классе данных.
Когда все определено, перейдите к файлу FeedPresenter.kt внутри пакета презентации и сначала добавьте в класс:
private val json = Json { ignoreUnknownKeys = true }
Это создаст Json
объект, который будет использоваться для декодирования содержимого файла. Важно установить ignoreUnknownKeys
, чтобы true
избежать каких-либо исключений, которые могут быть вызваны в случае, если одно из полей внутри RWContent.kt не имеет прямого атрибута в файле JSON. Не забудьте импортировать kotlinx.serialization.json.Json
.
Теперь обновите content
свойство, чтобы декодировать RW_CONTENT
вместо возврата emptyList
:
val content: List<RWContent> by lazy { json.decodeFromString(RW_CONTENT) }
content
лениво инициализируется. Другими словами, он откроет файл и прочитает его содержимое только при доступе. Когда это сделано, он вызывает decodeFromString
для создания списка RWContent
объектов.
Добавьте следующий импорт для разрешения decodeFromString
:
import kotlinx.serialization.decodeFromString
Пришло время собрать и запустить проект и посмотреть, что нового в Learn . Вы увидите экраны, похожие на следующие, на разных платформах:

Рис. 11.6 — Android-приложение для разных платформ

Рис. 11.7 — iOS-приложение с разными платформами

Рис. 11.8 — Десктопное приложение с разными платформами
Сериализуемый против Parcelable
Java имеет Serializable
интерфейс, расположенный в пакете java.io. Он использует отражение для чтения полей из объекта, что является медленным процессом, который часто создает множество временных объектов, которые влияют на объем памяти приложения.
С другой стороны Parcelable
, специфичный для Android эквивалент для Serializable
, требует объявления всех типов объектов. Это делает его более быстрым решением, поскольку нет необходимости использовать отражение для понимания типа объекта.
Кто-то может возразить, что Parcelable
это сложнее реализовать. Это было верно несколько лет назад, поскольку необходимо было переопределить пару методов и создать методы чтения/записи в соответствии с полями объекта. Но вам не нужно делать все это сейчас, так как плагин kotlin-parcelize генерирует этот код автоматически. Таким образом, единственное усилие здесь — добавить аннотацию в начало класса — @Parcelize
— и расширить Parcelable
.
Реализация Parcelize в KMP
Parcelable
и Parcelize
представляют собой набор классов, специфичных для платформы Android. Общий модуль содержит бизнес-логику приложения и его модели данных, которые используются на нескольких платформах. Поскольку этот код должен быть специфичным для платформы, вам нужно будет объявить его, используя ключевые слова expect и fact , которые вы уже использовали в главе 1, «Введение».
Parcelize
является частью плагина kotlin-parcelize , который содержит Parcelable
генератор кода. Прежде чем писать объявление Android для этого класса, вам нужно сначала добавить его в файл build.gradle.kts общего модуля . Откройте его, и в разделе добавьте:plugin
id("kotlin-parcelize")
Синхронизируйте проект и добавьте эту новую зависимость.
Чтобы установить класс данных как Parcelable , нужно добавить аннотацию @Parcelize
в начало объявления класса, а затем расширить Parcelable
генератор. Должно быть что-то похожее на:
import kotlinx.parcelize.Parcelize @Parcelize data class RWEntry(val id: String, val entry: String): Parcelable
Однако, поскольку этот специфичный для платформы код не должен существовать в commonMain , вам необходимо определить это поведение на уровне платформы — в данном случае в androidMain .
Определение ожидаемого/фактического целевого значения для Android
Как правило, имена классов, зависящих от платформы, начинаются с префикса Platform-. Это улучшает читаемость, упрощая идентификацию этих классов без необходимости перемещаться по всем пакетам, чтобы найти их.
Чтобы реализовать Parcelize , вам нужно объявить:
- Класс Parcelable , который расширяется классом данных.
- Аннотация Parcelize , которая используется для активации плагина kotlin-parcelize .
Перейдите к commonMain в общем модуле, перейдите к платформе и создайте файл PlatformParcelable.kt . Откройте его и добавьте:
package com.raywenderlich.learn.platform //1 expect interface Parcelable //2 @OptIn(ExperimentalMultiplatform::class) @OptionalExpectation //3 @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) //4 expect annotation class Parcelize()
Разобьем этот фрагмент кода на части:
- Интерфейс Parcelable , который необходимо определить.
- Объявление аннотации все еще экспериментальной, а это означает, что API может измениться в будущем. Эти две аннотации уведомляют пользователя об этом поведении и не позволяют Android Studio предупреждать разработчика каждый раз при вызове этого
Parcelize
класса. Target
иRetention
аннотации являются частьюParcelize
аннотации Котлина. Чтобы сохранить то же поведение, что и нативное, они также добавлены сюда.Parcelize
Аннотация, которая будет определена .
Примечание . При использовании
OptIn
аннотации вы можете увидеть предупреждающие сообщения в журнале сборки, подобные этому:Этот класс можно использовать только с аргументом компилятора ‘-opt-in=kotlin.RequiresOptIn’
Они печатаются, чтобы предупредить разработчика о том, что некоторые функции
OptIn
могут измениться или стать несовместимыми в будущем. Это то, с чем вам нужно быть осторожным, поскольку это означает, что вы можете в конечном итоге реорганизовать свой код, если он изменится. В любом случае, после подтверждения вы всегда можете удалить эти сообщения, добавив аргумент компилятора:-opt-in=kotlin.RequiresOptIn
.
Откройте общий файл build.gradle.kts и добавьте следующий код в конец этого файла:
kotlin.sourceSets.all { languageSettings.optIn("kotlin.RequiresOptIn") }
В зависимости от используемых вами классов данных вам может потребоваться создать ожидаемый / фактический класс для других аннотаций. Одним из таких примеров является @RawValue
, который используется вместе с сериализаторами по умолчанию для пользовательских типов. Вы можете использовать тот же подход, что и в Parcelize , для достижения той же цели:
@OptIn(ExperimentalMultiplatform::class) @OptionalExpectation @Target(AnnotationTarget.TYPE) @Retention(AnnotationRetention.BINARY) expect annotation class RawValue()
После определения ожидаемых объявлений перейдите к androidMain и создайте соответствующую фактическую реализацию. Он должен находиться в том же каталоге, что и файл, который вы только что добавили в commonMain .
Перейдите в каталог платформы и создайте соответствующий файл PlatformParcelable.kt . Откройте его и добавьте:
package com.raywenderlich.learn.platform actual typealias Parcelable = android.os.Parcelable actual typealias Parcelize = kotlinx.android.parcel.Parcelize
Так как Parcelable
они Parcelize
уже существуют на платформе Android, создавать их не нужно. Вместо этого используйте typealias
для создания ссылки между ожидаемым классом и самим фактическим классом. Другими словами, это как если бы вы вызывали android.os.Parcelable или kotlinx.android.parcel.Parcelize напрямую.
Примечание . Псевдонимы типов не создают новый тип данных, а вместо этого создают ссылку на существующий. Получите больше информации о typealias Kotlin прямо из официальной документации .
Работа с целями, которые не поддерживают Parcelize
Теперь, когда вы внедрили Parcelize на common и Android, если вы посмотрите на actual
поля, которые вы добавили, вы увидите красную подчеркивание, что означает, что что-то не так. Это происходит потому, что приложение нацелено на другие платформы, на которых отсутствует actual
реализация.
Перейдите в папку iosMain и внутри каталога платформы создайте соответствующий файл PlatformParcelable.kt .
Примечание . Нажмите Alt + Enter , и Android Studio автоматически предложит создать файлы для остальных платформ. Сгенерируйте эти файлы, нажав OK .
Откройте PlatformParcelable.kt и добавьте:
package com.raywenderlich.learn.platform actual interface Parcelable
Вот и все! iOS не имеет соответствующего интерфейса Parcelable , поэтому определите пустое объявление. Компилятор автоматически удаляет и аннотацию, и класс, так как нет набора объявлений. Сгенерированная структура не содержит ссылок на Parcelize
и Parcelable
.
Добавьте тот же файл в desktopMain/platform .
Это ожидаемое поведение, так как это специфичная функция только для Android.
Добавление Parcelize к существующим классам
Когда все настроено, перейдите в commonMain и найдите классы данных RWContent и RWEntry . Они находятся внутри пакета данных/модели .
В каждом из этих файлов добавьте @Parcelize
аннотацию над объявлением класса и расширьте Parcelable
:
Файл RWContent.kt будет выглядеть так:
@Parcelize @Serializable data class RWContent( val platform: PLATFORM, val url: String, val image: String ) : Parcelable
И RWEntry.kt будет выглядеть так:
@Parcelize data class RWEntry( val id: String = "", val link: String = "", val title: String = "", val summary: String = "", val updated: String = "", val imageUrl: String = "", val platform: PLATFORM = PLATFORM.ALL, val bookmarked: Boolean = false ) : Parcelable
Теперь вы можете без проблем отправлять этот объект по разным действиям.
Тестирование
Тесты подтверждают написанные вами предположения и дают вам важную подстраховку от всех будущих изменений.
Тестирование сериализации
Чтобы протестировать свой код, вам нужно перейти к общему модулю, щелкнуть правой кнопкой мыши папку src и выбрать « Создать ▸ Каталог » . В раскрывающемся списке выберите commonTest/kotlin . Здесь создайте класс SerializationTests.kt :
class SerializationTests { }
Вам потребуется создать тесты кодирования и декодирования, чтобы убедиться, что все работает должным образом. Начните с написания кодировщика. Добавьте в класс следующий метод:
@Test fun testEncodePlatformAll() { val data = RWContent( platform = PLATFORM.ALL, url = "https://www.raywenderlich.com/feed.xml", image = "https://assets.carolus.raywenderlich.com/assets/razeware_460-308933a0bda63e3e327123cab8002c0383a714cd35a10ade9bae9ca20b1f438b.png" ) val decoded = Json.encodeToString(RWContent.serializer(), data) val content = "{\"platform\":\"all\",\"url\":\"https://www.raywenderlich.com/feed.xml\",\"image\":\"https://assets.carolus.raywenderlich.com/assets/razeware_460-308933a0bda63e3e327123cab8002c0383a714cd35a10ade9bae9ca20b1f438b.png\"}" assertEquals(content, decoded) }
Здесь вы подтверждаете, что ваша сериализация JSON способна кодировать объект RWContentdata
в строку. Это свойство соответствует разделу all в Learn . Если сериализатор работает правильно, результат encodeToString
должен быть таким же, как content
— иначе тест провалится. Щелкните зеленую стрелку рядом с функцией слева, чтобы запустить тест, и выберите android (:testDebugUnitTest) . Вы увидите, что тест проходит.
Теперь, чтобы проверить правильность десериализации, добавьте следующий метод:
@Test fun testDecodePlatformAll() { val data = "{\"platform\":\"all\",\"url\":\"https://www.raywenderlich.com/feed.xml\",\"image\":\"https://assets.carolus.raywenderlich.com/assets/razeware_460-308933a0bda63e3e327123cab8002c0383a714cd35a10ade9bae9ca20b1f438b.png\"}" val decoded = Json.decodeFromString(RWContent.serializer(), data) val content = RWContent( platform = PLATFORM.ALL, url = "https://www.raywenderlich.com/feed.xml", image = "https://assets.carolus.raywenderlich.com/assets/razeware_460-308933a0bda63e3e327123cab8002c0383a714cd35a10ade9bae9ca20b1f438b.png" ) assertEquals(content, decoded) }
По сути делаешь наоборот. Начиная с ответа JSON, вам нужно будет вызвать kotlinx.serializationdecodeFromString
, чтобы построить ваш объект RWContent , а затем вы сравните его с тем, который вы ожидаете — . Если содержимое совпадает, тест успешно пройден. Запустите тест и убедитесь, что он проходит.decoded``content
Тестирование пользовательской сериализации
Чтобы протестировать свой собственный RWSerializer , вам сначала нужно определить:
private val serializers = serializersModuleOf(PLATFORM::class, RWSerializer)
Это serializers
свойство содержит сериализатор , который вам понадобится для кодирования и декодирования ваших данных.
Начните с создания теста кодирования:
@Test fun testEncodeCustomPlatformAll() { val data = PLATFORM.ALL val encoded = Json.encodeToString(serializers.serializer(), data) val expectedString = "\"all\"" assertEquals(expectedString, encoded) }
Когда вы получаете ответ, тело представляет собой строку ответа в формате JSON:
{ "platform":"all", "url":"https://www.raywenderlich.com/feed.xml", "image":"https://assets.carolus.raywenderlich.com/assets/razeware_460-308933a0bda63e3e327123cab8002c0383a714cd35a10ade9bae9ca20b1f438b.png" }
Чтобы проверить, правильно ли работает RWSerializer в приведенном выше тесте, проверьте, соответствует ли результат ответу JSON "all"
после кодирования PLATFORM.ALL
свойства в строку.
Если это так, assertEquals
функция вернет true, иначе тест не пройден.
Теперь добавьте тест декодера:
@Test fun testDecodeCustomPlatformAll() { val data = PLATFORM.ALL val decoded = Json.decodeFromString<PLATFORM>(data.value) assertEquals(decoded, data) }
Здесь вы делаете все наоборот. Из строки «все», возвращенной из data.value
, вы хотите получить соответствующее PLATFORM
значение перечисления. Для этого вы звоните decodeFromString
и подтверждаете, является ли возвращенный объект тем, который вы ожидаете.
Проблемы
Вот несколько задач, которые помогут вам попрактиковаться в том, что вы узнали в этой главе. Если вы застряли, взгляните на решения в материалах к этой главе.
Задача 1: загрузить RSS-канал
В настоящее время вы загружаете различные разделы из свойства RW_CONTENT внутри FeedPresenter.kt . В этом задании вы:
- Создайте свойство RW_ALL_FEED , содержащее содержимое RSS-канала одного из URL-адресов канала из RW_CONTENT .
- Прочтите это свойство и проанализируйте его содержимое в общем модуле, чтобы оно было доступно для использования всеми приложениями.
- В androidApp и desktopApp откройте файл FeedViewModel.kt внутри ui/home и заполните элементы этими новыми данными.
- В iOSApp откройте FeedClient внутри extensions и в функции fetchFeeds добавьте новые каналы.
Задача 2: добавьте тесты в свою реализацию
Теперь, когда вы внедрили эту новую функцию, вы добавите тесты, чтобы гарантировать правильность реализации. Не забудьте проверить сценарии, в которых некоторые атрибуты недоступны в файле JSON или их больше, чем доступно в RWEntry.kt .
Примечание . У вас должна быть возможность прочитать строку JSON, содержащую контент, который нужно сериализовать, и последующий доступ к объекту.
Ключевые моменты
- Обмен данными между локальными и удаленными приложениями требует, чтобы передаваемое содержимое было сериализовано и десериализовано в зависимости от того, отправляется оно или получено соответственно.
- kotlinx.serialization — это мультиплатформенная библиотека, поддерживающая сериализацию. Он позволяет сериализовать/десериализовать JSON , буферы протокола , CBOR , свойства , HOCON , YAML и Apache Avro .
- Вы можете создавать собственные сериализаторы, реализовав сериализацию/десериализацию для пользовательского типа, а затем связав его с этим классом.
- Вы можете использовать typealias с фактическим , чтобы автоматически связать объявление класса с существующим на уровне платформы.
Куда пойти отсюда?
Другие практические примеры использования Parcelize можно найти в статье.
Следующая глава начинается с функций, которые вы реализовали в этой, но вместо того, чтобы загружать данные только локально, вы также будете получать их из сети.