TimePicker in Kotlin Compose Multiplatform (KMP/KMM)

In today’s tutorial, you will learn how to implement a TimePicker in Kotlin Compose Multiplatform (KMP/KMM) for both Android & iOS platforms. We are implementing a platform-specific time picker that works with common compose code. On an Android device, it launches the Android native date picker dialog, while on iOS, it launches the native iOS date picker dialog.

TimePicker in Kotlin Compose Multiplatform (KMP/KMM)

TimePicker in Kotlin Compose Multiplatform (KMP/KMM):

One of the common hurdles developers face when diving into Kotlin Multiplatform (KMP) and Compose Multiplatform is handling UI components that don’t have a built-in, cross-platform equivalent. A prime example is the TimePicker. While essential for many apps, there’s no common TimePicker() Composable you can drop into your shared code.

So, what’s the solution? We lean on the core strength of KMP: accessing native platform APIs.

In this guide, we’ll walk you through the powerful expect/actual pattern to create a seamless, native TimePicker experience for both Android and iOS users. You’ll get the best of both worlds: a shared UI logic in Compose Multiplatform and the familiar, platform-specific UI that users expect.

Start coding for the app:

1. Add kotlinx-datetime dependency:

Open your project’s composeApp -> build.gradle.kts file and put “kotlinx-datetime” dependency in commonMain.dependencies {   } block. Please SYNC the project after this.

2. Android-specific TimePicker implementation:

1. Open your project’s APP -> composeApp -> src -> androidMain -> kotlin -> packageName -> MainActivity.kt file.

2. Implement pickTime() method.

Code explanation:

  1. actual fun pickTime(…): The actual keyword fulfills the promise made by expect. The compiler now knows this is the official Android version of the pickTime function.
  2. if (context !is Context) return: This is a crucial safety check. To show a dialog on Android, you absolutely need a Context. We check if the Any? object we received is actually a Context. If not, we exit the function to prevent a crash.
  3. val calendar = …: We get an instance of Calendar to fetch the current hour and minute. This is used to initialize the TimePickerDialog with the current time, which is a standard user experience.
  4. TimePickerDialog(…): This creates an instance of the standard, native Android time picker dialog.
  5. context: The required Android context.
  6. { _, selectedHour, selectedMinute -> … }: This is the listener that gets executed when the user clicks the “OK” button in the dialog. Inside the listener, String.format(…) is used to format the integer hour and minute into a two-digit string (e.g., 7 becomes 07, 15 stays 15), ensuring a consistent “HH:mm” format.
  7. onTimePicked(time): This is the key moment! We call the callback function we received as a parameter and pass the newly formatted time string. This sends the data back to our shared UI.
  8. hour, minute: The initial time to display in the picker.
  9. false: This is for the is24HourView parameter. false means it will show a 12-hour clock with an AM/PM toggle.
  10. .show(): This final call displays the dialog to the user.

3. Pass the Android context in the Main Activity:

Complete source code of MainActivity.kt file:

3. iOS-specific TimePicker implementation:

1. Open your project’s APP -> composeApp -> src -> iosMain -> kotlin -> packageName -> pickTime.kt file. You have to create a new file named pickTime.kt.

Code explanation:

  1. actual fun pickTime(…): Just like on Android, this actual function fulfills the expect contract for iOS.
  2. val timePicker = UIDatePicker().apply {…}: This creates an instance of the native iOS picker view.
  3. datePickerMode = …Time: Configures it to show only time, not a date.
    preferredDatePickerStyle = …Wheels: Sets the visual style to the classic rotating wheels.
  4. val alertController = …: In iOS, you typically present a picker inside another container. UIAlertController is a common choice. The message = “\n\n…” is a well-known trick to create empty space inside the alert where we can place our timePicker.
  5. alertController.view.addSubview(timePicker): This adds the picker view as a child of the alert view.
  6. Constraints (.centerXAnchor, .topAnchor etc.): This is programmatic UI layout in iOS (AutoLayout). These lines center the timePicker horizontally and position it near the top of the alert’s view.
  7. alertController.addAction(…): This adds a “Done” button to the alert.
  8. val formatter = NSDateFormatter(): This is the iOS tool for converting dates and times to and from strings. We configure it with dateFormat = “HH:mm” to get our desired 24-hour format.
  9. formatter.stringFromDate(timePicker.date): This takes the selected date/time object from the picker and converts it into our formatted string.
  10. onTimePicked(selectedTime): The callback is invoked, sending the formatted time string back to our shared KMP code.
  11. …presentViewController(…): This is the final command that finds the currently active screen (rootViewController) and presents our alert with the time picker on top of it.

2. Now we have to pass null at the place of context in APP -> composeApp -> src -> iosMain -> kotlin -> packageName -> MainViewController.kt file.

4. Common TimePicker Implementation:

1. Open your project and create a file: APP -> composeApp -> src -> commonMain -> kotlin -> packageName -> time_picker.kt file.

Code explanation:

  1. expect fun pickTime(…): This is the core of the KMP pattern.
    The expect keyword is a promise. It tells the KMP compiler, “I am defining the signature of a function here, but not its implementation. I expect that every platform I compile for (like Android and iOS) will provide its own actual implementation of this exact function.”
  2. context: Any?: This parameter is a generic placeholder. In commonMain, we don’t know about Android’s Context or iOS’s UIViewController. So we use Any?, the supertype of all types in Kotlin, to create a flexible parameter that can accept whatever platform-specific object we need to pass in later. The ? makes it nullable, as iOS might not need it.
  3. onTimePicked: (String) -> Unit: This is a high-order function, also known as a callback. It’s a function that we pass as a parameter. Its purpose is to send data back from the platform-specific code to our common UI code. Once the native Android or iOS picker selects a time, it will call this function, passing the selected time as a String.

5. Common TimePicker Implementation in App.kt file:

Screenshots on an Android device:

Screenshots on an iOS device:

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *