Compose Multiplatform is a powerful framework that allows developers to create cross-platform applications with shared UI logic. However, platform-specific features, like notifications or toast messages, often require a custom approach. This blog explores the concept of bridging in Compose Multiplatform and provides detailed examples of implementing toast notifications on Android, iOS, and Desktop platforms.
We'll cover:
- Why bridging is essential in Compose Multiplatform.
- Pros and cons of this approach.
- Detailed code snippets for platform-specific implementations.
- Key takeaways for developers.
Why Bridging in Compose Multiplatform? 🤔
Imagine a world where you can create beautiful UIs for Android, iOS, Desktop, and Web without switching frameworks. Compose Multiplatform makes this dream a reality by enabling code-sharing while maintaining platform-specific experiences.
However, some features demand special handling. That’s where Compose Multiplatform Bridging comes in! Bridging helps create platform-specific implementations while staying within the Compose ecosystem, ensuring apps look and feel native while leveraging shared business logic.
Before You Begin 🧑💻
To make the most out of this guide, we recommend understanding the
expect
andactual
keywords in Kotlin. Check out this detailed blog: Exploringexpect
andactual
in Kotlin .
Pros and Cons of Compose Multiplatform Bridging 🌟
Pros
- ✅ Code Reusability: Share UI logic and reduce duplication.
- ✅ Native Experience: Implement platform-specific features without breaking abstraction.
- ✅ Faster Development: Focus on core functionality while Compose handles the UI.
- ✅ Reduced Maintenance: Unified UI logic across platforms.
Cons
- ❌ Learning Curve: Requires understanding both Compose and platform-specific APIs.
- ❌ Debugging Complexity: Bugs may arise at the intersection of shared and platform-specific code.
- ❌ Limited Resources: Compose Multiplatform is still evolving, so documentation can be sparse.
Bridging Sample Code: Toast Notifications Across Platforms 🛠️
Let’s see how to display toast notifications on Android, iOS, and Desktop using bridging.
Common Platform.kt
File
This file defines the Platform interface and the expected ShowToast function:
import androidx.compose.runtime.Composable
interface Platform {
val name: String
}
expect fun getPlatform(): Platform
//our new EXPECT function
@Composable
expect fun ShowToast(message: String)
The Platform.kt
file defines the
Platform
interface and two key functions:
-
getPlatform()
: This is present by default while creating a project and it is anexpect
function, meaning its implementation will vary by platform. -
ShowToast()
: A composable function for displaying toast messages. It is also declared withexpect
, requiring platform-specific implementations.
This structure ensures that the interface and method declarations remain consistent across platforms.
Platform-Specific Implementations 🌐
Android (Platform.android.kt
)
Displays a simple Android Toast using LocalContext.
import android.content.Context
import android.os.Build
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
class AndroidPlatform : Platform {
override val name: String = "Android ${Build.VERSION.SDK_INT}"
}
actual fun getPlatform(): Platform = AndroidPlatform()
@Composable
actual fun ShowToast(message: String) {
val context: Context = LocalContext.current
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
In the Android-specific file:
-
Platform Name: The class
AndroidPlatform
provides the platform's name usingBuild.VERSION.SDK_INT
, indicating the Android version. -
ShowToast: The function uses Android’s native
Toast.makeText
to display a simple toast notification. TheLocalContext
provides the requiredContext
dynamically.
This implementation ensures native-like toast notifications on Android devices.
iOS (Platform.ios.kt
)
Creates a toast-like view and animates its fade-out.
import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.interop.LocalUIViewController import androidx.compose.ui.platform.LocalWindowInfo import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.useContents import platform.CoreGraphics.CGRectMake import platform.UIKit.* class IOSPlatform : Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion } actual fun getPlatform(): Platform = IOSPlatform() @OptIn(ExperimentalForeignApi::class, ExperimentalComposeUiApi::class) @Composable actual fun ShowToast(message: String) { val toastLabel = UILabel().apply { text = message textColor = UIColor.whiteColor backgroundColor = UIColor.blackColor.colorWithAlphaComponent(0.6) textAlignment = NSTextAlignmentCenter font = UIFont.systemFontOfSize(14.0) numberOfLines = 0 layer.cornerRadius = 10.0 clipsToBounds = true } val maxWidth = LocalWindowInfo.current.containerSize.width.toDouble() val maxHeight = LocalWindowInfo.current.containerSize.height.toDouble() toastLabel.setFrame(CGRectMake(maxWidth / 4, maxHeight / 2, maxWidth / 2, 50.0)) LocalUIViewController.current.view.addSubview(toastLabel) UIView.animateWithDuration( 1.5, delay = 2.0, options = UIViewAnimationOptionCurveEaseInOut, animations = { toastLabel.alpha = 0.0 }, completion = { toastLabel.removeFromSuperview() } ) }
The iOS-specific file leverages UIKit for toast creation:
-
Platform Name: The class
IOSPlatform
fetches the iOS system name and version usingUIDevice
. -
ShowToast:
-
A
UILabel
is dynamically created to act as the toast message. Its properties, such as text color, background color, and font, are configured. - The label size is calculated using the screen’s dimensions and scaled appropriately to fit the message.
- The label is added as a subview to the main view, and an animation is applied to fade it out after a delay.
-
A
This approach replicates a toast-like behavior natively on iOS, ensuring smooth UI integration.
Desktop (Platform.desktop.kt
)
Uses Java’s SystemTray or a JOptionPane fallback for notifications.
import androidx.compose.runtime.Composable
import java.awt.SystemTray
import java.awt.Toolkit
import java.awt.TrayIcon
import javax.swing.JOptionPane
@Composable
actual fun ShowToast(message: String) {
if (SystemTray.isSupported()) {
val tray = SystemTray.getSystemTray()
val image = Toolkit.getDefaultToolkit().createImage("logo.webp")
val trayIcon = TrayIcon(image, "Desktop Notification")
tray.add(trayIcon)
trayIcon.displayMessage("Info", message, TrayIcon.MessageType.INFO)
} else {
JOptionPane.showMessageDialog(null, message, "Desktop Notification", JOptionPane.INFORMATION_MESSAGE)
}
}
For Desktop, the implementation utilizes Java’s AWT library:
-
SystemTray Support: If the system supports
SystemTray
, a tray notification is displayed using aTrayIcon
. -
Fallback: On systems without tray support, a
JOptionPane
is used to display a modal dialog with the message.
This dual approach ensures compatibility across a wide range of desktop environments.
Using the Toast in App.kt
🍞
This Compose code lets users display a toast message with the click of a button:
@Composable
@Preview
fun App() {
MaterialTheme {
var toastContent by remember { mutableStateOf("") }
Box(
modifier = Modifier.fillMaxSize().background(color = Color.Red)
) {
if (toastContent.isNotBlank()) {
ShowToast(toastContent)
toastContent = ""
}
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = { toastContent = "This is a Toast Message!" }) {
Text("Show Toast")
}
}
}
}
}
The App.kt
file integrates the platform-specific
implementations within a common Compose UI:
-
Toast Logic: The
toastContent
state variable triggers theShowToast
function when updated. -
UI Components: A
Box
acts as the container, while aColumn
centers the button horizontally. The button updates thetoastContent
, showing the toast message when clicked.
This composable function demonstrates seamless integration of platform-specific functionality within a unified Compose Multiplatform UI.
Key Takeaways for Developers 🎯
- Compose Multiplatform bridges the gap between shared and native UIs, empowering developers to write once and customize as needed.
-
Platform-specific implementations like
ShowToast
allow apps to retain their native feel. - Developers must leverage bridging to handle device-specific nuances, ensuring their apps shine on every platform!
Post a Comment