11.
Serialization
Written by Carlos Mota
Great job on completing the first two sections of the book! You’re doing great. Now that you’re familiar with Kotlin Multiplatform, you have everything you need to tackle the challenges of this last section.
Here, you’ll start making a new app called learn. It’s built on top of the concepts you learned in the previous chapters, and it introduces a new set of concepts, too: serialization, networking and how to handle concurrency.
learn uses the raywenderlich.com RSS feeds to show the latest tutorials for Android, iOS, Unity and Flutter. You can add them to a read-it-later list, share them with a friend, search for a specific key, or just browse through everything the team has released. It will have the same look and feel you’re already used to from the raywenderlich.com website.
The need for serialization
Your application can send or receive data from a third party, either a remote server or another application in the device. Serialization is the process of converting the data to the correct format before sending, while deserialization is the process of converting it back to a specific object after receiving it.
There are different types of serialization formats — for instance, JSON or byte streams. You’ll read more about this in Chapter 12, “Networking,” when covering network requests.
Android uses this concept to share data across activities, services or receivers — either in the same application or to third-party apps. The difference is that instead of relying on Serializable to send data from custom types, the OS requires you to implement Parcelable to send these objects.
Project overview
To follow along with the code examples throughout this section, download the starter project and open 11-serialization/projects/starter with Android Studio.
Note: This project uses Jetpack Compose 1.2.0-alpha01 and Multiplatform Compose 1.1.0 with Kotlin 1.6.10. To build and run the app successfully, use Android Studio Bumblebee 2021.1.1 Patch 2 or a newer version.
Starter has the skeleton of the app you’ll build, and final gives you something to compare your code to when you’re done.
After the project synchronizes, you’ll see a set of subfolders and other important files:
The next section explains the project tree hierarchy in detail. The folder names are self-explanatory and correspond to either the platform they are used for or the functionality itself. You’re welcome to skip it and go directly to the Application features section.
Android app
Located inside androidApp, the Android app contains the Gradle configuration files, the app source code and its resources. It’s the same structure that you’re already used to from your Android apps, and you can use any library or component as you typically do:
- components: generic Composable functions that represent a specific purpose and are used in different screens.
- ui: contains all the UI. The sub packages correspond to specific screens (bookmark, home, latest or search), the navigation bar (main), the application design system (theme) or utility classes (util).
- RWApplication.kt: the Application class that initializes the Context needed by SQLDelight.
Build and run the app.
This is your app skeleton. It doesn’t look much, but that’s because there’s no data to show. You’ll load the app data in the next sections.
iOS app
Your iOS app is inside the iOSApp folder. Navigate to this folder and in the root directory and enter:
pod install
This allows all the project dependencies to be fetched.
Once done, it’s time to open the project. Use Xcode or AppCode to open the file iosApp.xcworkspace located inside the iosApp folder.
Note: There are two different iosApp.* files: xcworkspace, generated by CocoaPods, and xcodeproj, generated by Xcode. You need to open the first one so the IDE can resolve the project dependencies properly.
You don’t need any additional steps to run the app. Open the iosApp target, navigate to the Build Phases tab, and then select the Run Script dropdown. Here you have:
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode
This task automatically compiles the SharedKit framework and adds it to your project when necessary.
After the project is synchronized, compile and run the app. You’ll see a screen like this:
Desktop application
The desktop application is similar to the Android app. The code was copied from one project to the other with just a couple of small changes — namely, on the libraries used that weren’t available for the JVM target:
- Kamel: Image loading library.
- precompose: A community library that lets you use Jetpack Lifecycle, ViewModel, LiveData and Navigation in a desktop application.
Although these libraries are available at the above links, they’re not updated to the latest versions of Kotlin or Compose. That’s why they’re included in this project. You can find more information about this in Appendix C.
To run the desktop application, go to the command line and in the project root folder, enter:
./gradlew desktopApp:run
After it finishes, a new window will open with the app. You’ll see a screen like this:
Note: Use the mouse to change the window size.
Shared module
This contains the entire business logic of learn. It’s the multiplatform code that’s shared across Android, iOS and desktop.
You’ll find:
-
androidMain: Definition of Android platform-specific code.
-
androidTest: Tests written for the platform-specific code mentioned above.
-
commonMain: Contains the business logic that’s going to be shared across all the platforms targeted.
-
commonTest: Contains all non-platform-specific tests created to validate if the business logic is working as expected.
-
iosMain: Definition of iOS platform-specific code.
-
iostTest: Tests written for the platform-specific code mentioned above.
Common code
When you open the shared module, you’ll see two things inside commonMain:
- kotlin: Where the application business logic is.
- sqldelight: Contains the SQL file that’s going to be used by SQLDelight to create the app’s database, along with the corresponding CRUD methods to interact with it.
This module follows the clean architecture paradigm, grouping files according to their responsibility in the business logic. Open the kotlin directory and you’ll see:
-
data: Networking layer of the shared module. Fetches the RW feeds and defines the data model of each RSS entry.
-
domain: Deserializes the response and creates a list of feeds that can later be consumed by the UI. Saves data into the database and defines the callbacks that are going to be used to notify when new data is available.
-
platform: Declares which functionalities need to be implemented on each platform. It represents the platform-specific code of the app, and it’s where you’re going to find the expect keyword.
-
presentation: This layer makes the bridge between the UI and the app logic.
-
ServiceLocator: A singleton object that provides access to the different modules within the app. It’s also responsible to initialize all the Presenters along with the properties that they require.
In the next section, you’ll see what learn will look like after you implement all the required features.
Application features
Before starting to write code, have a look first at the app concept and its features:
Don’t worry about the details on each screen. You’ll have a chance to see them more closely during the next chapters.
learn has four different screens that you can navigate to from the app’s bottom bar:
Home
This is the app’s default screen. It shows a horizontal list with all the raywenderlich.com topics and a list of the latest articles published.
These topics work as a filter. Clicking any item redirects the user to the latest screen where they can see the most recent articles written, read or shared, then add them to the bookmarks list or remove them once they’re done.
To open one, click on the card. You’ll be automatically redirected to the browser, where you can access and read it. If you click on the three dots inside the card instead, the app will show a bottom sheet. From there, you can add it to your read-it-later list, available from the bookmarks screen, or send it to a friend so they can be up to speed on the latest articles.
Bookmarks
This screen shows all the articles that you’ve saved. Is the list getting big? Pick one and start reading it. Afterward, you can remove it from this list by clicking the three dots on the card and selecting Remove from bookmarks.
Latest
This features a more graphical interface with the sections and covers of the latest articles. You can either scroll horizontally to see its content or vertically to switch across different topics.
Search
There’s a lot of content that you can browse. Here, you can filter by a specific keyword and finally find that article you’ve been looking for.
Now that you’re familiar with the app and the project, it’s time to start learning how to implement these features.
Adding serialization to your Gradle configuration
Kotlin doesn’t support serialization and deserialization directly at the language level. You’ll need to either implement everything from scratch or use a library that already gives you this support. Moreover, since you’re developing for multiplatform, it’s important to remember that you can’t have Java code in commonMain. Otherwise, the project won’t compile. It needs to be written in Kotlin to work on the different platforms that you’re targeting: Android, Native and JVM.
No, there’s no need to implement this support. It’s already available on kotlinx.serialization, a library created and maintained by JetBrains.
Note: Read more about kotlinx.serialization from the official documentation. Or, dive into the source code, available on its official repository.
Time to import this library into the app. Open Android Studio and wait for the project to finish synchronizing. In the project root folder, there’s a build.gradle.kts file; open it and add the following classpath:
classpath("org.jetbrains.kotlin:kotlin-serialization:1.6.10")
With this declaration, the build system knows where it should search and fetch the serialization library.
Now, open the build.gradle.kts file inside shared and load the serialization plugin by adding the following code inside the plugins block:
kotlin("plugin.serialization")
Synchronize and wait for this process to finish. Once ready, the system will load the library and add it to the project.
There are four different build.gradle.kts files in the project:
-
build.gradle.kts: This is located in the project root folder and configures both the Android app and the shared module. It contains the repositories from which dependencies will be downloaded.
-
shared/build.gradle.kts: This is the shared module configuration file. It contains the plugins that are going to be used, libraries, as well as which platforms you’re going to target.
-
androidApp/build.gradle.kts: The configuration file for the Android app. Defines a set of parameters used to compile the project, namely the SDK version, dependencies and compilations flags.
-
desktopApp/build.gradle.kts: This is the configuration file for the desktop application. It defines the same configuration as the Android build.gradle.kts file.
Different serialization formats
kotlinx.serialization supports a set of serialization formats outside the box:
-
JSON: A lightweight human-readable data-interchange format. learn uses them to load the RW RSS feed links from a local file and later to deserialize the data received from the server (kotlinx-serialization-json).
-
Protocol buffers: A cross-platform mechanism for serialized structured data (kotlinx-serialization-protobuf).
-
CBOR: A binary data serialization format (kotlinx-serialization-cbor).
-
Properties: A key-value file that saves the configuration parameters of an application used in Java-related technologies. In Android development, you have a file named gradle.properties that contains properties to configure your Gradle daemon/your app at compile time (kotlinx-serialization-properties).
-
HOCON: A superset of JSON that’s more human-readable and typically used in configuration files (kotlinx-serialization-hocon).
Note: There are also two community-maintained libraries for YAML and Apache Avro.
Except for kotlinx-serialization-json, all of these libraries are still experimental. Although they all seem robust and are used in a set of applications, it’s worth mentioning that the API can (drastically) change in future releases — meaning that you might end up refactoring its usage.
During the scope of this book, you’ll only need to add kotlinx-serialization-json. Navigate to shared, open the build.gradle.kts file and search for the commonMain field. There’s already a set of dependencies on the project. At the end of this list, add:
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
Synchronize the project, and now you’re ready to use serialization for JSON format.
Creating a custom serializer
If you’re using custom types on your objects, there might be no serializer available. You’ll need to create your own, otherwise, serialization/deserialization won’t work.
A good example of this is on the RWContent.kt data class, inside the shared module. The platform field is of type PLATFORM, an enum created to identify which section the article belongs to.
If you don’t create a custom serializer, kotlinx-serialization-json won’t be able to identify what the keywords “All,” “Android,” “iOS,” “Unity” or “Flutter” are. This happens because the attribute on the JSON is type String, and it should be PLATFORM instead. A custom serializer/deserializer needs to be implemented to provide this support.
In the commonMain package inside the shared module, go over to data and create a RWSerializer.kt file.
Start by adding the findByKey method:
private fun findByKey(key: String, default: PLATFORM = PLATFORM.ALL): PLATFORM {
return PLATFORM.values().find { it.value == key } ?: default
}
This will receive a key and return the corresponding value in the enum PLATFORM.
Now that you’ve added the mapping function, create the RWSerializer object. In the same file, add:
//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
}
}
}
Here’s a step-by-step breakdown of this logic:
-
The serialization API is still experimental. Every time you use it, Android Studio automatically underlines its call, asking to either use
OptInorRequireannotations. This error is shown to notify the developer that this API may change in the future. In this project, you’ll to useOptInto avoid adding a new annotation on every call to this class. -
Associate the
RWSerializerto a specific class. In this scenario, it corresponds to the enumPLATFORM. -
You’ll need to extend the
KSerializerclass and define the type of object that is going to be serialized/deserialized. -
It’s necessary to define the
PrimitiveSerialDescriptiorthat contains the class name —PLATFORM— and how the parameter should be read. In this case, it’s going to be from aString. -
Now that everything is ready, it’s time to define the
serializemethod. Since, the content type ofPLATFORMisString, you just need to callencodeStringand send thevalueof the received object. -
Finally, to deserialize, you’re going to call the opposite method, which is
decodeString, and with the raw value (the key), you’ll callfindByKeyto see which value of the enum it corresponds to.
That’s it! RWSerializer is ready. You just need to add it to the class. Open the RWContent.kt file again, and above the declaration of PLATFORM add:
@Serializable(with = RWSerializer::class)
This associates the new device serializer to the PLATFORM class. Remember to import from kotlinx.serialization.Serializable.
Serializing/deserializing new data
Navigate to FeedPresenter.kt inside commonMain/presentation, and you’ll see a RW_CONTENT property. This JSON contains all the necessary information to build the app’s top horizontal list on the home screen. Its structure has the following attributes:
-
platform: The different areas covered by raywenderlich.com articles: Android, iOS, Unity and Flutter. There’s a fifth value — all. Once set, it removes this filter and shows everything published.
-
url: Contains the RSS feed URL from where the articles should be fetched.
-
image: A cover image that corresponds to the
platformthat was selected.
These three attributes are already mapped into the data class RWContent.kt, which is inside data/model. Open it and add to the top of its declaration:
@Serializable
The RWContent data class uses @Serializable, so when decoding the RW_CONTENT property, it easily generates a list of RWContent that maps the attributes on the JSON string into the fields in the data class.
With everything defined, navigate to the FeedPresenter.kt file inside the presentation package and first add to the class:
private val json = Json { ignoreUnknownKeys = true }
This will create the Json object that’s going to be used to decode the file content. It’s important to set ignoreUnknownKeys to true to avoid any exceptions that might be thrown in case one of the fields inside RWContent.kt doesn’t have a direct attribute in the JSON file. Remember to import kotlinx.serialization.json.Json.
Now, update the content property to decode the RW_CONTENT instead of returning an emptyList:
val content: List<RWContent> by lazy {
json.decodeFromString(RW_CONTENT)
}
content is lazily initialized. In other words, it will open the file and read its content only when accessed. When done, it calls decodeFromString to generate a list of RWContent objects.
Add the following import to resolve decodeFromString :
import kotlinx.serialization.decodeFromString
It’s time to build and run the project and see what’s new in learn. You’ll see screens similar to the following ones on different platforms:
Serializable vs. Parcelable
Java has a Serializable interface located in the java.io package. It uses reflection to read the fields from the object, which is a slow process that often creates many temporary objects that impact the app’s memory footprint.
On the other hand Parcelable, an Android specific equivalent for Serializable, requires all the object types to be declared. This makes it a faster solution, since there’s no need to use reflection to understand the object type.
One might argue that Parcelable is more complex to implement. This was true some years ago, since it was necessary to override a couple of methods and create the read/write methods according to the object fields. But, you don’t have to do all that now, as the kotlin-parcelize plugin generates this code automatically. So the only effort here is to add an annotation to the top of the class — @Parcelize — and extend Parcelable.
Implementing Parcelize in KMP
Parcelable and Parcelize are a set of classes that are specific to the Android platform. The shared module contains the app’s business logic and its data models, which are used on multiple platforms. Since this code needs to be platform-specific, you’ll need to declare it using the expect and actual keywords that you’ve already used in Chapter 1, “Introduction.”
Parcelize is part of a plugin named kotlin-parcelize, which contains the Parcelable code generator. Before writing the Android declaration for this class, you’ll need to first add it to the shared module’s build.gradle.kts file. Open it, and in the plugin section add:
id("kotlin-parcelize")
Synchronize the project and add this new dependency.
To set a data class as Parcelable, one would add the annotation @Parcelize to the top of the class declaration and then extend the Parcelable generator. It should be something similar to:
import kotlinx.parcelize.Parcelize
@Parcelize
data class RWEntry(val id: String, val entry: String): Parcelable
However, since this platform-specific code shouldn’t exist on commonMain, you’ll need to define this behavior at the platform level — in this case in androidMain.
Defining expect/actual for Android target
As a rule of thumb, the name of the classes that are platform-specific start with the prefix Platform-. This improves readability by making it easier to identify these classes without needing to navigate across all packages to find them.
To implement Parcelize, you need to declare:
-
The class Parcelable, which is extended by the data class.
-
The annotation Parcelize, which is used to activate the kotlin-parcelize plugin.
Go to commonMain in the shared module, navigate to platform and create a PlatformParcelable.kt file. Open it and add:
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()
Let’s break this code snippet into parts:
-
The Parcelable interface that needs to be defined.
-
Declaring an annotation it’s still experimental, meaning that the API might change in the future. These two annotations notify the user about this behavior and prevent Android Studio from warning the developer every time there’s a call to this
Parcelizeclass. -
TargetandRetentionannotations are part of Kotlin’sParcelizeannotation. To keep the same behavior as the native one, they’re also added here. -
The
Parcelizeannotation that will be defined.
Note: When using the
OptInannotation, you might see warning messages on your build log similar to this one:This class can only be used with the compiler argument ‘-opt-in=kotlin.RequiresOptIn’
They are printed to warn the developer that there are features using
OptInthat might change or be incompatible in the future. This is something that you’ll need to be careful with, since it means that you may end up refactoring your code if it changes. In any case, after you acknowledge it, you can always remove these messages by adding the compiler argument:-opt-in=kotlin.RequiresOptIn.
Open the shared build.gradle.kts file and add the following code at the end of this file:
kotlin.sourceSets.all {
languageSettings.optIn("kotlin.RequiresOptIn")
}
Depending on the data classes that you’re using, you might need to create an expect/ actual class for other annotations. One of these examples is @RawValue, which is used along default serializers for custom types. You can follow the same approach used on Parcelize to achieve the same goal:
@OptIn(ExperimentalMultiplatform::class)
@OptionalExpectation
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
expect annotation class RawValue()
With the expected declarations defined, go over to androidMain and create the corresponding actual implementation. It should be located in the same directory as the file you’ve just added on commonMain.
Navigate to the platform directory and create the corresponding PlatformParcelable.kt file. Open it and add:
package com.raywenderlich.learn.platform
actual typealias Parcelable = android.os.Parcelable
actual typealias Parcelize = kotlinx.android.parcel.Parcelize
Since Parcelable and Parcelize already exist on the Android platform, there’s no need to create them. Instead, use typealias to create a reference link between the expected class and the actual class itself. In other words, it’s as if you’re calling android.os.Parcelable or kotlinx.android.parcel.Parcelize directly.
Note: Type aliases don’t create a new type of data, but instead create a link to an existing one. Get more information about Kotlin’s typealias directly from the official documentation.
Working with targets that don’t support Parcelize
Now that you’ve implemented Parcelize on common and Android, if you look at the actual fields that you’ve added, you’ll see a red underline, which means that something is wrong. This happens because the app is targeting other platforms that are missing the actual implementation.
Go to the iosMain folder and inside the platform directory, create the corresponding PlatformParcelable.kt file.
Note: Press Alt + Enter, and Android Studio automatically suggests creating the files for the remaining platforms. Generate these files by pressing OK.
Open PlatformParcelable.kt and add:
package com.raywenderlich.learn.platform
actual interface Parcelable
That’s it! iOS doesn’t have a corresponding Parcelable interface, so define an empty declaration. The compiler automatically removes both the annotation and class since there’s no declaration set. The generated framework doesn’t contain any reference to Parcelize and Parcelable.
Add the same file to desktopMain/platform.
This is the expected behavior, since this is an Android-only specific feature.
Adding Parcelize to existing classes
With everything set, go to commonMain and search for the RWContent and RWEntry data classes. They’re inside the data/model package.
In each of these files, add the @Parcelize annotation above the class declaration and extend Parcelable:
The RWContent.kt file will look like this:
@Parcelize
@Serializable
data class RWContent(
val platform: PLATFORM,
val url: String,
val image: String
) : Parcelable
And, RWEntry.kt will look like this:
@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
Now, you can start sending this object across different activities without any problems.
Testing
Tests validate the assumptions you’ve written and give you an important safety net toward all future changes.
Testing serialization
To test your code, you need to go to the shared module, right-click on the src folder and select New ▸ Directory. In the drop-down, select commonTest/kotlin. Here, create a SerializationTests.kt class:
class SerializationTests { }
You’ll need to create encode and decode tests to validate that everything is working as expected. Start by writing the encoder. Add the following method to the class:
@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)
}
Here, you’re validating that your JSON serialization is capable of encoding a RWContent object, data, to a string. This property corresponds to the all section in learn. If the serializer is working correctly, the result of encodeToString needs to be the same as content — otherwise the test will fail. Click the green arrow by the function on the left to run the test and select android (:testDebugUnitTest). You’ll see the test passes.
Now to test if the deserialization is correct, add the following method:
@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)
}
Essentially, you do the opposite. Starting with the JSON response, you’ll need to call decodeFromString so kotlinx.serialization builds your RWContent object, decoded, and then you’ll compare it with the one that you’re expecting - content. If the content is the same, the test successfully passes. Run the test and see that it passes.
Testing custom serialization
To test your custom RWSerializer, you first need to define:
private val serializers = serializersModuleOf(PLATFORM::class, RWSerializer)
This serializers property contains the serializer you’ll need to encode and decode your data.
Start by creating an encoding test:
@Test
fun testEncodeCustomPlatformAll() {
val data = PLATFORM.ALL
val encoded = Json.encodeToString(serializers.serializer(), data)
val expectedString = "\"all\""
assertEquals(expectedString, encoded)
}
When you receive a response, the body is a string response in a JSON format:
{
"platform":"all",
"url":"https://www.raywenderlich.com/feed.xml",
"image":"https://assets.carolus.raywenderlich.com/assets/razeware_460-308933a0bda63e3e327123cab8002c0383a714cd35a10ade9bae9ca20b1f438b.png"
}
To test if RWSerializer is working correctly on the test above, check if the result corresponds to the JSON response "all" after encoding the PLATFORM.ALL property into a string.
If it does, the assertEquals function will return true, otherwise the test will fail.
Now, add the decoder test:
@Test
fun testDecodeCustomPlatformAll() {
val data = PLATFORM.ALL
val decoded = Json.decodeFromString<PLATFORM>(data.value)
assertEquals(decoded, data)
}
Here, you’re doing the opposite. From the string “all”, returned from data.value, you want the corresponding PLATFORM enum value. For that, you call decodeFromString and confirm if the returned object is the one you’re expecting.
Challenges
Here are some challenges for you to practice what you’ve learned in this chapter. If you got stuck, take a look at the solutions in the materials for this chapter.
Challenge 1: Load an RSS feed
You’re currently loading the different sections from the RW_CONTENT property inside FeedPresenter.kt. In this challenge, you will:
-
Create an RW_ALL_FEED property that contains the RSS feed content of one of the feed URLs from RW_CONTENT.
-
Read this property and parse its content in the shared module so it can be available for all apps to use.
-
In androidApp and desktopApp, open the FeedViewModel.kt file inside ui/home, and populate items with this new data.
-
In iOSApp, open FeedClient inside extensions, and in the fetchFeeds function add the new feeds.
Challenge 2: Add tests to your implementation
Now that you’ve implemented this new feature, you’ll add tests to guarantee your implementation is correct. Don’t forget to test scenarios where some attributes are not available on the JSON file or there are more than the ones available in RWEntry.kt.
Note: You should be able to read a JSON string containing the content to be serialized and access to the object afterward.
Key points
-
Exchanging data between local and remote applications requires the content that’s transferred be serialized and deserialized, depending on if it’s being sent or received, respectively.
-
kotlinx.serialization is a multiplatform library that supports serialization. It allows serializing/ deserializing JSON, Protocol buffers, CBOR, Properties, HOCON, YAML and Apache Avro.
-
You can create custom serializers by implementing the serialize/ deserialize for a custom type and then associating it with that class.
-
You can use typealias with actual to automatically link a class declaration to an existing one at the platform level.
Where to go from here?
For other practical examples where Parcelize is used, read the Kotlin Android Extensions article.
The next chapter starts with the features that you’ve implemented in this one, but instead of only loading the data locally, you’ll also fetch it from the network.