Build future-proof cross-platform apps with these battle-tested practices
Poorly structured Kotlin Multiplatform projects often face:
<a href="https://androidboss.info/kotlin-multiplatform-mastering-platform-specific-code-with-expect-actual/" data-type="post" data-id="415">expect/actual</a> UsageShared Interface (commonMain)
expect interface BiometricAuthenticator {
fun authenticate(): Boolean
}
Platform Implementations
// Android
actual class AndroidBiometricAuth : BiometricAuthenticator {
actual override fun authenticate() =
BiometricPrompt(...).authenticate()
}
// iOS
actual class IosBiometricAuth : BiometricAuthenticator {
actual override fun authenticate() =
LAContext().canEvaluatePolicy(...)
}
Best Practices:
*/src/androidMain and */src/iosMainshared/
├── core/ # Networking, database, utils
│ ├── network/
│ └── database/
├── features/ # Feature modules
│ ├── auth/
│ └── profile/
└── app/ # App entry points
Key Benefits:
class UserViewModel : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users
fun loadUsers() {
viewModelScope.launch {
_users.value = try {
userRepository.fetchUsers() // Suspend function
} catch (e: Exception) {
handleError(e)
emptyList()
}
}
}
}
| Dispatcher | Use Case | Example |
|---|---|---|
Dispatchers.Default | Complex calculations | JSON parsing, data transforms |
Dispatchers.IO | Network/disk I/O | Database queries, API calls |
Dispatchers.Main | UI updates | Compose state changes |
build.gradle.kts Configuration:
plugins {
id("org.jlleitschuh.gradle.ktlint") version "11.6.1"
}
ktlint {
disabledRules.set(setOf("import-ordering"))
filter {
exclude("**/generated/**")
}
}
Checks to Enable:
KDoc Template:
/**
* Fetches user profile from backend
*
* @param userId UUID generated during registration
* @return [User] object with profile data
* @throws AuthException If JWT token is invalid
*/
suspend fun getProfile(userId: String): User
Common Leak Sources:
Prevention:
DisposableEffect(Unit) {
val sensor = SensorManager()
sensor.start()
onDispose { sensor.stop() } // Critical for iOS/Android
}
| Platform | Tool | Key Metric |
|---|---|---|
| Android | Android Studio Profiler | Memory heap allocations |
| iOS | Xcode Instruments | Thread utilization |
| Web | Chrome DevTools | JS heap size |
sealed class AppError {
data class Network(val code: Int) : AppError()
data class Database(val message: String) : AppError()
object AuthExpired : AppError()
}
fun handleError(error: AppError) {
when (error) {
is AppError.Network -> showSnackbar("Check internet connection")
is AppError.Database -> logCrashlytics(error.message)
AppError.AuthExpired -> navigateToLogin()
}
}
Multiplatform Setup:
expect <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Logger</span> </span>{ fun debug(message: String) fun error(throwable: Throwable) } // Android actual actual <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AndroidLogger : Logger</span> </span>{ actual override fun debug(message: String) = Log.d(<span class="hljs-string">"App"</span>, message) } // iOS actual actual <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IosLogger : Logger</span> </span>{ actual override fun debug(message: String) = NSLog(<span class="hljs-string">"<span class="hljs-variable">%@</span>"</span>, message) } Internal Links
External Links
Introduction: Transform Your Cross-Platform Development with Material Design 3 Are you ready to revolutionize your… Read More
Jetpack Compose 1.8 rolls out handy features like Autofill integration, slick Text enhancements including auto-sizing… Read More
Reified Keyword in Kotlin: Simplify Your Generic Functions Kotlin's reified keyword lets your generic functions know the… Read More
Android Studio Cloud: Ditch the Setup, Code Anywhere (Seriously!) Alright, fellow Android devs, gather 'round… Read More