Compose Multiplatform bridging the Platform Divide: using Expect/Actual mechanism

Compose Multiplatform bridging the Platform Divide: using Expect/Actual mechanism

Compose Multiplatform lets you create stunning UIs for Android, iOS, Desktop, and Web. With the expect/actual mechanism, you can write shared logic while tailoring platform-specific logics seamlessly.

🚀 Understanding Expect/Actual in Kotlin Multiplatform

The expect/actual mechanism makes cross-platform magic happen! 🪄

  • expect: Declare a function or property in common code that needs platform-specific handling.
  • actual: Define how it works on each platform (Android, iOS, etc.).

Write shared logic, let platforms handle the rest.

🎯 Benefits of Expect/Actual

1️⃣ Shared Codebase: Write core logic once and reuse it across platforms, saving time and effort.

2️⃣ Native Specific: Use platform-specific components for the best user experience.

3️⃣ Better Testing: Keep platform logic separate, making it easier to test your shared code.

Example 1. Sample Code for a Cross-Platform Local DateTime Formatting

Common Code (commonMain/com/example/DateTime.kt):**

expect object DateTime {
    fun LocalDateTime.format(format: String): String
    fun getDateTime(string: String, format: String): LocalDateTime
}

Android Code (androidMain/com/example/DateTime.kt):

actual object DateTime {
    actual fun LocalDateTime.format(
        format: String
    ): String =
        SimpleDateFormat(format, Locale.getDefault()).format(
            toJavaLocalDateTime().toKotlinLocalDateTime().toInstant(
                TimeZone.currentSystemDefault()
            ).toEpochMilliseconds()
        )

    @SuppressLint("NewApi")
    actual fun getDateTime(string: String, format: String): LocalDateTime {
        val formatter = DateTimeFormatter.ofPattern(format)
        val zonedDateTime = ZonedDateTime.parse(string, formatter)
        return zonedDateTime.toLocalDateTime().toKotlinLocalDateTime()
    }
}

iOS Code (iosMain/com/example/DateTime.kt):

actual object DateTime {
    actual fun LocalDateTime.format(format: String): String {
        val dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = format

        return dateFormatter.stringFromDate(this.toInstant(TimeZone.currentSystemDefault()).toNSDate())
    }

    actual fun getDateTime(string: String, format: String): LocalDateTime {
        val dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = format
        val date = dateFormatter.dateFromString(string)
            ?: throw IllegalStateException("Could not convert string to NSDate $string")
        return date.toKotlinInstant().toLocalDateTime(TimeZone.currentSystemDefault())
    }
}

Usage:

val today = getLocalDateTimeFromLong(getCurrentLocalTimestamp()).format("dd.MM.yyyy hh:mm a")

In your composable screens within the common codebase, you can use the DateTime formatting and getDateTime functions with your desired formats.

Example 2. Sample Code for a Cross-Platform decimal formatting

Common Code (commonMain/com/example/Formatter.kt):

expect fun formatNumber(number: Double): String

Android Code (androidMain/com/example/Formatter.kt):

import java.text.DecimalFormat

actual fun formatNumber (number: Double): String{
    val formatter = DecimalFormat("#,###")
    return formatter.format(number)
}

iOS Code (iosMain/com/example/Formatter.kt):

import platform.Foundation.NSNumber
import platform.Foundation.NSNumberFormatter
import platform.Foundation.NSNumberFormatterDecimalStyle

actual fun formatNumber (number: Double): String{
    val numberFormatter = NSNumberFormatter()
    numberFormatter.numberStyle = NSNumberFormatterDecimalStyle
    return numberFormatter.stringFromNumber(NSNumber(number)) ?: ""
}

Usage:

Text(
    text = formatNumber(item().value), // Decimal
    style = MaterialTheme.typography.bodyMedium,
    color = MaterialTheme.colorScheme.onPrimary,
    fontWeight = FontWeight.Bold,
    maxLines = 1,
    overflow = TextOverflow.Ellipsis
)

In your composable screens within the common codebase, you can call the formatNumber function with your desired values. The platform-specific implementation will seamlessly handle.

Conclusion:

The expect/actual mechanism helps you build efficient and easy-to-maintain cross-platform apps with Compose Multiplatform. By following these tips and learning the core ideas, you can smoothly connect platforms and create a great experience for users on any device.

💡 #Kotlin #Compose #Multiplatform #DevTips