Reading Local JSON Files in Kotlin Multiplatform Mobile (KMM)

Karim Reda
arconsis
Published in
6 min readAug 8, 2023

Introduction

Kotlin Multiplatform Mobile (KMM) empowers developers to write shared code that can be utilized across multiple platforms, including Android and iOS. One common requirement in mobile app development is reading data from a local JSON file. Whether it’s configuration data, static content, or any structured information, reading local JSON files in KMM applications can be achieved seamlessly.

In this article, we will explore how to read a local JSON file in Kotlin Multiplatform Mobile, covering the necessary steps and providing code examples.

What are JSON files?

JSON (JavaScript Object Notation) file is a commonly used file format for storing and exchanging data. JSON is a lightweight and human-readable format that is easy for both humans and machines to understand. It has become the de facto standard for data interchange in many web and mobile applications.

A JSON file consists of key-value pairs organized in a hierarchical structure. It is primarily used to represent structured data, such as configuration settings, user preferences, or API responses. JSON files are plain text files with a .json extension and can be easily created and parsed using various programming languages.

What are Local resources?

Local resources in mobile app development refer to files or assets that are bundled with the app and stored locally on the device. These resources are typically included in the app package during the development process and are accessible to the app without requiring an internet connection or network access.

Advantages of Reading Local JSON Files

  1. Offline Accessibility: By including JSON files as local resources within the app package, you ensure that the data is readily available even without an internet connection. This allows the app to function seamlessly in offline scenarios, providing a consistent user experience.
  2. Improved Performance: Reading local JSON files from local resources eliminates the need for making network requests or downloading data from remote servers. This reduces latency and improves app performance, as the data can be quickly accessed and processed locally.
  3. Data Integrity: Local JSON files are bundled with the app and are not subject to network-related issues or changes in the remote data source. This ensures data integrity and consistency, as the app always relies on the pre-defined JSON files present within the app package.
  4. Easier App Updates: When app updates or new versions are released, local JSON files can be updated and included in the updated app package. This allows for easy and controlled updates to the app’s data without relying on server-side changes or API updates.
  5. Customization and Personalization: Local JSON files enable app developers to include customizable or personalized data within the app. Users can modify or update the JSON files locally, providing a level of customization and personalization to their app experience.
  6. Enhanced Security: Local JSON files are stored locally on the device and are not exposed to external network vulnerabilities. This provides an additional layer of security for sensitive or confidential data that needs to be stored and accessed within the app.
  7. Language Localization: JSON files can be utilized for language localization purposes, allowing developers to include localized strings or content within the app. Reading localized JSON files from local resources ensures that the app can provide a localized user interface and content, catering to users from different regions and languages.
  8. Simplicity and Flexibility: Reading local JSON files is a straightforward process, as it involves reading the file from the local resources directory and parsing it into a usable data format. JSON’s flexible and human-readable syntax makes it easy to work with and manipulate the data as needed within the app.

Reading Local JSON Files in Kotlin Multiplatform Mobile (KMM)

To read the local JSON file in your KMM project, follow the steps outlined below.

Step 1: Set up your KMM project

Create a new KMM project using the instructions provided by JetBrains. Ensure you have the necessary project structure and configuration for iOS and Android platforms.

If you’re unfamiliar with the process of creating a KMM project, we encourage you to refer to our article on getting started with Kotlin Multiplatform Mobile (KMM). It provides detailed guidance on the topic

Step 2: Adding the JSON File to the Project

Before reading a local JSON file, ensure that it is included in your project’s shared module. Place the JSON file within the resources directory of the shared module. Make sure the file is properly bundled with the app during the build process.

If you haven’t created a resource directory yet, you can create one by following these instructions:
1. Within the commonMain directory, create a new directory called “resources
2. Right-click on the “resources” directory, choose “Mark Directory as”, and select “Resources Root” to designate it as the resources root directory.

Creating a new resource directory

Step 3: Loading the JSON File

To read the local JSON file from the shared module, use the appropriate platform-specific API to access the file’s contents. On Android, you can utilize the ClassLoader to open an input stream, and on iOS, you can use the Bundle API to retrieve the file’s URL. This can be accomplished using the expected and actual mechanism offered by Kotlin Multiplatform Mobile (KMM).

For more insights on how to write platform-specific code in the shared module, we encourage you to refer to our article on Expected and Actual Mechanism in Kotlin Multiplatform Mobile (KMM).

Here’s an example of loading the JSON file in the shared module:

// in src/commonMain/kotlin
internal expect class SharedFileReader() {
fun loadJsonFile(fileName: String): String?
}


// in src/androidMain/kotlin
internal actual class SharedFileReader{
actual fun loadJsonFile(fileName: String): String? {
return javaClass.classLoader?.getResourceAsStream(name).use { stream ->
InputStreamReader(stream).use { reader ->
reader.readText()
}
}
}
}

// in src/iosMain/kotlin
internal actual class SharedFileReader{
private val bundle: NSBundle = NSBundle.bundleForClass(BundleMarker)

actual fun loadJsonFile(fileName: String): String? {
val (filename, type) = when (val lastPeriodIndex = fileName.lastIndexOf('.')) {
0 -> {
null to fileName.drop(1)
}
in 1..Int.MAX_VALUE -> {
fileName.take(lastPeriodIndex) to fileName.drop(lastPeriodIndex + 1)
}
else -> {
fileName to null
}
}
val path = bundle.pathForResource(filename, type) ?: error("Couldn't get path of $fileName (parsed as: ${listOfNotNull(filename, type).joinToString(".")})")

return memScoped {
val errorPtr = alloc<ObjCObjectVar<NSError?>>()

NSString.stringWithContentsOfFile(path, encoding = NSUTF8StringEncoding, error = errorPtr.ptr) ?: run {
error("Couldn't load resource: $fileName. Error: ${errorPtr.value?.localizedDescription}")
}
}
}

private class BundleMarker : NSObject() {
companion object : NSObjectMeta()
}
}

Step 4: Parsing the JSON File

Once the JSON file is loaded as a string, you can parse it into an object representation using a JSON parsing library. Kotlin provides a built-in JSON library called kotlinx.serialization, which simplifies the parsing process.

Here’s an example of parsing the JSON file using kotlinx.serialization:

import kotlinx.serialization.*
import kotlinx.serialization.json.*

@Serializable
data class User(val name: String, val age: Int)

fun parseJsonFile(jsonString: String): User? {
return Json.decodeFromString<User>(jsonString)
}

Step 5: Error Handling

When reading a local JSON file, it’s essential to handle potential errors such as file not found or parsing issues. You can employ exception handling to gracefully handle these scenarios and provide appropriate error handling or fallback mechanisms.

Here’s an example of how to handle errors in the shared module code:

private val sharedFileReader: SharedFileReader = SharedFileReader()
fun loadAndParseJsonFile(): User? {
val jsonString = sharedFileReader.loadJsonFile() ?: return null

return try {
parseJsonFile(jsonString)
} catch (e: Exception) {
// Handle parsing error
null
}
}

By following these steps, you can easily read a local JSON file from within the shared module in Kotlin Multiplatform Mobile. This approach allows you to centralize your JSON reading logic and provides a consistent experience across different platforms, such as Android and iOS.

Conclusion

Reading local JSON files is a common task in mobile app development, and Kotlin Multiplatform Mobile provides an efficient way to achieve this across Android and iOS platforms. By following the steps outlined in this article, you can seamlessly load and parse local JSON files in your shared KMM code. Whether you’re working with configuration files, static content, or any structured data, KMM empowers you to create consistent and streamlined experiences for users on different platforms.

--

--