Why Creating a Splash Screen is Not Really a Kotlin Multiplatform Problem
Understanding Platform-Specific Implementations for Android and iOS
Kotlin Multiplatform (KMP) has emerged as a versatile tool for sharing code across various platforms, including Android, iOS, desktop, web, and server environments. While it allows developers to write common code once and reuse it across different systems, it is important to understand the boundaries of shared code and the necessity for platform-specific implementations.
💡 Although Kotlin Multiplatform now encompasses a wide range of platforms, it is often implicitly understood to focus on Android and iOS development, reminiscent of the former Kotlin Multiplatform Mobile (KMM).
Understanding how platform-specific features like splash screens are implemented can guide developers on the right learning path and problem-solving approach. This knowledge helps avoid unnecessary complications often found in tutorials labelled under “Kotlin Multiplatform” that may not accurately reflect the nuances of real-world development.
This article is based on my experience developing my KMP app “OctoMeter,” which I built publicly and use daily. Here, I share my firsthand experience to clarify how splash screens and other platform-specific features are effectively handled within KMP.
The Role of Kotlin Multiplatform and Compose Multiplatform
Kotlin Multiplatform aims to streamline the development process by allowing developers to share business logic and core functionality between platforms, minimising code duplication and inconsistencies. Compose Multiplatform, based on Jetpack Compose for Android, extends this approach to UI development, enabling developers to use the same UI code for Android and iOS.
However, KMP and Compose Multiplatform do not eliminate the need for native platform setups and configurations. Elements like splash screens, part of the app initialisation process, must be set up in the native project environment.
Why Splash Screens Are Independent of KMP and Compose Multiplatform on iOS and Android
The setup for a splash screen involves platform-specific configurations and resources not handled by the shared code in a KMP project. This is because the splash screen appears before the application’s main UI is loaded, making it independent of any cross-platform logic or UI code.
iOS Splash Screen Setup
On iOS, the splash screen is referred to as the launch screen. It is configured using a storyboard or a launch screen image in Xcode. A regular iOS Developer without Kotlin Multiplatform knowledge should have no difficulties setting this up.
If we have generated the base project using Kotlin Multiplatform Wizard and are happy with the initial setup, we can open /iosApp/iosApp.xcodeproj
using XCode to set up the launch screen.
There is more than one way to define the launch screen, but in OctoMeter, I used the storyboard approach.
- Create LaunchScreen.storyboard: Use Xcode to create a
Launch Screen.storyboard
file. For this, we can apply a Launch Screen template. - Configure the Launch Screen: Customize the layout to match the desired splash screen design. We can add images, labels, and other UI elements here.
- Associate the Launch Screen:
Go to iosApp
> General
> Targets: iosApp
> App Icons and Launch Screen
Ensure thatLaunch Screen.storyboard
is selected as the Launch Screen file.
For more detailed instructions, refer to the Apple Launch Screen Developer Documentation.
💡 This iOS setup does not involve any Kotlin Multiplatform or Compose Multiplatform code, as it runs before the shared code is initialised.
Android Splash Screen Setup
The recommended way to implement a splash screen on Android is by using the Jetpack SplashScreen library. This library simplifies the process and ensures consistency across different Android versions. Again, this is not new to an ordinary Android Developer and has nothing to do with Kotlin Multiplatform or Compose Multiplatform.
Like iOS, if we have generated the base project using Kotlin Multiplatform Wizard and are happy with the initial setup, we can import the project into Android Studio, and start from the provided sample code.
1. Add Dependency: First, include the SplashScreen library in composeApp/build.gradle.kts
. Optionally, we may migrate this to the Version Catalog.
sourceSets {
androidMain.dependencies {
implementation("androidx.core:core-splashscreen:1.0.1")
}
2. Create a Splash Theme: Define a theme for the splash screen in composeApp/src/androidMain/res/values/styles.xml
. I prefer themes.xml
, but it makes no difference. We can hardcode the colours here, but normally I would define them separately in colors.xml
. Also, we need to have the splash screen icon ready.
3. Update AndroidManifest.xml: Apply the splash theme to the main activity in composeApp/src/androidMain/AndroidManifest.xml
:
<application
...
android:theme="@style/Theme.Kunigami.Starting">
<activity ...>
4. Handle the Splash Screen: In the composeApp/src/androidMain/kotlin/.../MainActivity
, ensure that the splash screen is handled properly:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
super.onCreate(savedInstanceState)
setContent {
App()
}
}
}
Here, we can see that
installSplashScreen()
is called before the Compose Multiplatform UI is initialised in the project code generated by the Kotlin Multiplatform Wizard. The splash screen has nothing to do with the common code and UI residing in/commonMain/
.
For more detailed instructions, refer to the Android core-splashscreen documentation.
Other Platform-Specific Scenarios
Besides splash screens, which are explicitly platform-dependent, many other aspects of mobile app development require platform-specific implementations. For example, handling local notifications, checking device battery level, and similar tasks all necessitate platform-specific code.
In KMP projects, handling these tasks often involves writing code similar to what we would write without KMP. The expect/actual mechanism allows us to define interfaces in the common module and implement them for each platform, meaning these tasks have no unique “KMP” common code. In other words, we do not have a single implementation that can run on all supported platforms; we need to implement them separately for each platform.
💡 Generally, KMP libraries do the “dirty work” by wrapping platform-dependent code using
expect/actual
and exposing the function interfaces for us to access in the common code. There is no magic that we can expect a single common function to perform these tasks. This means we need to know the exact implementation for each platform to make the common code work. This explains why not all KMP libraries support all KMP platforms and why developing KMP libraries can be challenging.
The platform-specific code on Android should mostly resemble the case without KMP. However, when it comes to iOS, there is a distinction because the code is not written in Swift or Objective-C directly within Xcode but in Kotlin using Kotlin Native. This difference is primarily in syntax and language, as Kotlin Native provides interoperability with iOS libraries, allowing us to call native APIs from Kotlin.
Example: Retrieving the Operating System Name
A simple example of using expect/actual
is retrieving the operating system name, which we cannot do in a single common function:
🟩 Shared Code (Common Module)
expect fun getOperatingSystemName(): String
🟩 Android Implementation
actual fun getOperatingSystemName(): String {
return "Android ${android.os.Build.VERSION.RELEASE}"
}
🟩 iOS Implementation
import platform.UIKit.UIDevice
actual fun getOperatingSystemName(): String {
val device = UIDevice.currentDevice
return "${device.systemName} ${device.systemVersion}"
}
🟩 Using the Function in Common Code
fun printOperatingSystemName() {
val osName = getOperatingSystemName()
...
}
Wrapping Up
Creating a splash screen in Kotlin Multiplatform (KMP) projects illustrates a key point: certain aspects of mobile app development remain inherently platform-specific, and understanding these boundaries is essential for effective development. While KMP provides a powerful framework for sharing code and logic across platforms, it does not eliminate the need for platform-specific implementations when dealing with features like splash screens, notifications, or device-specific functionalities.
The expect/actual mechanism in KMP offers a structured way to handle platform-specific differences, allowing developers to maintain a clean and organised codebase. However, this requires a solid understanding of the application's shared and platform-specific parts. As demonstrated, tasks like configuring splash screens or retrieving system-specific information are implemented directly on each platform, leveraging native capabilities while maintaining a consistent interface in the shared code.
By recognising that some tasks will always require native handling, we can better manage our expectations, acknowledging that not everything in a Kotlin Multiplatform project has a KMP-specific common implementation.