Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing
Tags: multiplatform, kotlin, testing, android
Published: 2023-05-12 05:18:00

---
title: Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing
published: true
date: 2023-05-11 15:53:39 UTC
tags: Multiplatform,Kotlin,Testing,Android
canonical_url: https://akjaw.com/kotlin-multiplatform-writing-platform-specific-implementations-with-contract-testing/
---

 ![Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing](https://akjaw.com/content/images/2023/05/multiplatform-platform-implementation-1.png)

This article was originally posted on [my blog](https://akjaw.com/kotlin-multiplatform-writing-platform-specific-implementations-with-contract-testing/), for better viewing experience try it out there.

Even though The Kotlin Multiplatform ecosystem is getting bigger every day, there are cases where we need to write platform specific implementations by hand. Either because it's too specific to our domain, or because of legal reasons.

In these cases, we will need to write multiple implementations ourselves. Besides the implementation, we will also need to verify that all the implementations work in the same way between platforms. Otherwise, the shared code might be unpredictable and produce different behavior between platforms.

In this article, I'll cover how to write platform specific implementations for retrieving the device language and formatting days of the week depending on this language. To verify that both implementations work correctly, I'll show how you can write one test to cover all the implementations, reducing boilerplate and increasing alignment between platforms.

The project repository is on [GitHub](https://github.com/AKJAW/Kotlin-Native-Implementations?ref=akjaw.com), however due to [iOS testing issues with Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/issues/3135?ref=akjaw.com), Compose was commented out in order to fix the tests. This [branch](https://github.com/AKJAW/Kotlin-Native-Implementations/tree/enable-compose?ref=akjaw.com) enables the UI, but breaks tests.

## How to write platform specific code

There are two main ways of achieving this, I'll try to summarize each briefly.

### Expect Actual

This is the [official way](https://kotlinlang.org/docs/multiplatform-connect-to-apis.html?ref=akjaw.com) of providing platform specific implementations, it is nice for top level helper function or data structures with common properties, but also some platform specific ones (required by the platform).

The downside of this approach is that these implementations are final, and in most cases this makes them hard to replace with a Test Double when testing.

### Shared Interface with platform specific implementations

In this case, there is an interface which establishes a "contract" that the platforms need to follow through their implementation.

The platform specific implementations can be written in two ways:

1. Using the Native programming language, (e.g. Swift implementation that is passed in from the iOS platform to the shared code)
2. Using Kotlin, while targeting a specific platform: Kotlin/JVM, Kotlin/Native, Kotlin/JS.

The first option might be valid for complex implementations which might be hard to write using Kotlin, like Cryptography. However, this solution is harder to maintain, because it requires separate tests in Kotlin and in the Native language.

The second option requires using "native APIs" (like _platform.Foundation_) from Kotlin, which might be difficult. The upside is that it is easier to test and all the code resides in the shared codebase, making it easier to maintain and reason about.

Because the shared code depends on an interface, it makes it easier to replace with a Test Double when testing. Production apps use the real implementations, and the test code can use a Fake implementation. Because of this, the article will use the latter approach.

## Providing the device language

These implementations are pretty straight forward, so I won't get into the details too much. In the common code, we have an interface and a data class:

```kotlin
data class Language(val value: String)

interface LocaleProvider {

    fun getLanguage(): Language
}
```

And both platform implementations are one-liners:

```kotlin
import java.util.Locale

class AndroidLocaleProvider : LocaleProvider {

    override fun getLanguage(): Language = 
        Language(Locale.getDefault().language)
}

import platform.Foundation.NSLocale
import platform.Foundation.currentLocale
import platform.Foundation.languageCode

class IosLocaleProvider : LocaleProvider {

    override fun getLanguage(): Language =
        Language(NSLocale.currentLocale.languageCode)
}
```

These simple implementations allow us to retrieve the device language based on the phone settings.

However, we still need a way of instantiating these implementations, and currently, this can't be done in the common code. We can, however, create an expect actual function which will create "an instance" of the interface:

```kotlin
expect fun createLocaleProvider(): LocaleProvider

actual fun createLocaleProvider(): LocaleProvider = 
    AndroidLocaleProvider()

actual fun createLocaleProvider(): LocaleProvider = 
    IosLocaleProvider()
```

These functions can be used for creating the production implementation and then introducing it to the dependency graph, or for testing the production implementations.

## Formatting the day of the week

Now that we have a way of retrieving the device language, we can format the day based on that language.  The next implementations are a little bit more complex, but should be still easy to understand.  Just like previously, we start out with an interface:

```kotlin
interface DayOfWeekNameProvider {

    fun getLongName(dayNumber: Int): String?
}

expect fun createDayOfWeekNameProvider(
    localeProvider: LocaleProvider
): DayOfWeekNameProvider
```

The Android implementation uses the java.util.Calendar, which is pretty much what would be done in native Android apps:

```kotlin
class AndroidDayOfWeekNameProvider(
    private val localeProvider: LocaleProvider
) : DayOfWeekNameProvider {

    override fun getLongName(dayNumber: Int): String? {
        val calendar = Calendar.getInstance()
        calendar.set(Calendar.DAY_OF_WEEK, dayNumber)
        val locale = Locale(localeProvider.getLanguage().value)
        return calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, locale)
    }
}

actual fun createDayOfWeekNameProvider(
    localeProvider: LocaleProvider
): DayOfWeekNameProvider =
    AndroidDayOfWeekNameProvider(createLocaleProvider())

```

The Kotlin / Native implementation is more out of the ordinary, because we need to connect the Kotlin world with the iOS world:

```kotlin
class IosDayOfWeekNameProvider(
    private val localeProvider: LocaleProvider
) : DayOfWeekNameProvider {

    override fun getLongName(dayNumber: Int): String? {
        val dateFormatter = NSDateFormatter().apply {
            this.locale = NSLocale(localeProvider.getLanguage().value)
        }
        val index = dayNumber % 7
        return dateFormatter.weekdaySymbols[index].toString()
    }
}

actual fun createDayOfWeekNameProvider(
    localeProvider: LocaleProvider
): DayOfWeekNameProvider =
    IosDayOfWeekNameProvider(localeProvider)
```

As you can see, both implementations are still pretty easy to understand and are fully written in Kotlin. The best part is that they work:

 ![Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing](https://akjaw.com/content/images/2023/05/Screenshot-2023-05-08-at-19.14.42.png)

 ![Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing](https://akjaw.com/content/images/2023/05/Screenshot-2023-05-08-at-19.08.13.png)

_English locale_

 ![Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing](https://akjaw.com/content/images/2023/05/Screenshot-2023-05-08-at-19.13.26.png)

 ![Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing](https://akjaw.com/content/images/2023/05/Screenshot-2023-05-08-at-19.09.26.png)

_German locale_

However, there is a problem with the current implementation, can you spot it...?

On start-up, the both apps show a different day of the week. For Android, day 1 is Sunday and for iOS it is Monday. If we released this app to production, the user experience would be different between platforms. Additionally, it could cause unexpected behavior if we were to save the days to a database or send them to an API.

## Verifying platform implementations

### Test class for each implementation

The first solution for verifying these two implementations could be writing a test for each implementation. This is ok, but it has some drawbacks:

- More code means more maintenance. When changing something, the work is multiplied for each platform (tests + implementation).
- It is easy to forget about the other platforms, without due diligence, the tests could be misaligned and pass even though the platform implementations are different

Keeping in mind, we want those two implementations to behave in the same way, it would be best to have one test that verifies all the platforms. This can be achieved by writing a **Contract test**.

### Contract test for verifying all platforms

A Contract test is written once in commonTest, and depending on the target, it uses the corresponding implementation for that platform. A contract test for the _DayOfWeekNameProvider_ can be as follows:

```kotlin
class DayOfWeekNameProviderContractTest {

    @Test
    fun `Day number 1 is Monday`() =
        longNameTestCase(dayNumber = 1, "Monday")

    @Test
    fun `Day number 2 Tuesday`() =
        longNameTestCase(dayNumber = 2, "Tuesday")

    @Test
    fun `Day number 3 Wednesday`() =
        longNameTestCase(dayNumber = 3, "Wednesday")

    @Test
    fun `Day number 4 Thursday`() =
        longNameTestCase(dayNumber = 4, "Thursday")

    @Test
    fun `Day number 5 Friday`() =
        longNameTestCase(dayNumber = 5, "Friday")

    @Test
    fun `Day number 6 Saturday`() =
        longNameTestCase(dayNumber = 6, "Saturday")

    @Test
    fun `Day number 7 Sunday`() =
        longNameTestCase(dayNumber = 7, "Sunday")

    private fun longNameTestCase(dayNumber: Int, expectedName: String) {
        val sut =
            createDayOfWeekNameProvider(FakeLocaleProvider("en"))
        
        val result = sut.getLongName(dayNumber)
        
        assertEquals(expectedName, result)
    }
}

```

The above test could be improved by also verifying a different language and testing out some [boundary case values](https://akjaw.com/members/testing-mistakes-part-3/) like -1, 0 or 8. The test also uses a testCase function, which is one way of doing [parameterized tests on Kotlin Multiplatform](https://akjaw.com/kotlin-multiplatform-parameterized-tests-and-grouping/).

The test results for the current implementation look like this:

![Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing](https://akjaw.com/content/images/2023/05/Screenshot-2023-05-11-at-12.33.15.png)

The iOS tests pass, however the Android implementation is shifted by one day, so fixing it is just a matter of adding + 1 to the day number:

```kotlin
override fun getLongName(dayNumber: Int): String? {
    val calendar = Calendar.getInstance()
    calendar.set(Calendar.DAY_OF_WEEK, dayNumber + 1)
    val locale = Locale(localeProvider.getLanguage().value)
    return calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, locale)
}
```

![Kotlin Multiplatform: Writing Platform Specific Implementations with Contract Testing](https://akjaw.com/content/images/2023/05/Screenshot-2023-05-11-at-15.39.01.png)

## Summary

Hopefully by now, you have an idea of how platform implementations can be created. Writing everything in Kotlin might seem awkward at first, but there are many benefits for doing it, as I hopefully laid out in this article.

Because everything is in Kotlin, we can verify all implementations with one contract test that ensure that all the implementations are aligned and work in the same expected way.

Such contract testing can be used anywhere where we have multiple implementations, like Production and Fakes or Flavor specific implementations.