Skip to content

Instantly share code, notes, and snippets.

@ArnyminerZ
Last active October 12, 2023 10:19
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ArnyminerZ/31b82f559e3bfe42ef336f72854c5854 to your computer and use it in GitHub Desktop.
Save ArnyminerZ/31b82f559e3bfe42ef336f72854c5854 to your computer and use it in GitHub Desktop.
Date picker for Jetpack Compose - Material Design 3

Based on the answer of Joao Gavazzi at StackOverflow. I have adapted it to work with lower API levels, and Material Design 3.

Example usage:

var showPicker by remember { mutableStateOf(false) }
if (showPicker)
    DatePicker(onDateSelected = {

    }, onDismissRequest = {
        showPicker = false
    })
Button(onClick = { showPicker = true }) {
    Text(text = "Date picker")
}

Note that there are some TODOs to be fulfilled before using the DatePicker correctly.

import android.text.format.DateFormat
import android.view.ContextThemeWrapper
import android.widget.CalendarView
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import java.util.Calendar
import java.util.Date
/**
* A Jetpack Compose compatible Date Picker.
* @author Arnau Mora, Joao Gavazzi
* @param minDate The minimum date allowed to be picked.
* @param maxDate The maximum date allowed to be picked.
* @param onDateSelected Will get called when a date gets picked.
* @param onDismissRequest Will get called when the user requests to close the dialog.
*/
@Composable
fun DatePicker(
minDate: Long? = null,
maxDate: Long? = null,
onDateSelected: (Date) -> Unit,
onDismissRequest: () -> Unit
) {
val selDate = remember { mutableStateOf(Calendar.getInstance().time) }
// todo - add strings to resource after POC
Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) {
Column(
modifier = Modifier
.wrapContentSize()
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(size = 16.dp)
)
) {
Column(
Modifier
.defaultMinSize(minHeight = 72.dp)
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
)
.padding(16.dp)
) {
// TODO: Hardcoded text
Text(
text = "Select date",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onPrimary
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = DateFormat.format("MMM d, yyyy", selDate.value).toString(),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onPrimary
)
Spacer(modifier = Modifier.size(16.dp))
}
CustomCalendarView(
minDate,
maxDate,
onDateSelected = {
selDate.value = it
}
)
Spacer(modifier = Modifier.size(8.dp))
Row(
modifier = Modifier
.align(Alignment.End)
.padding(bottom = 16.dp, end = 16.dp)
) {
Button(
onClick = onDismissRequest,
colors = ButtonDefaults.textButtonColors(),
) {
//TODO - hardcode string
Text(
text = "Cancel",
)
}
Button(
onClick = {
val newDate = selDate.value
onDateSelected(
// This makes sure date is not out of range
Date(
maxOf(
minOf(maxDate ?: Long.MAX_VALUE, newDate.time),
minDate ?: Long.MIN_VALUE
)
)
)
onDismissRequest()
},
colors = ButtonDefaults.textButtonColors(),
) {
//TODO - hardcode string
Text(
text = "OK",
)
}
}
}
}
}
/**
* Used at [DatePicker] to create the calendar picker.
* @author Arnau Mora, Joao Gavazzi
* @param minDate The minimum date allowed to be picked.
* @param maxDate The maximum date allowed to be picked.
* @param onDateSelected Will get called when a date is selected.
*/
@Composable
private fun CustomCalendarView(
minDate: Long? = null,
maxDate: Long? = null,
onDateSelected: (Date) -> Unit
) {
// Adds view to Compose
AndroidView(
modifier = Modifier.wrapContentSize(),
factory = { context ->
CalendarView(ContextThemeWrapper(context, R.style.CalenderViewCustom))
},
update = { view ->
if (minDate != null)
view.minDate = minDate
if (maxDate != null)
view.maxDate = maxDate
view.setOnDateChangeListener { _, year, month, dayOfMonth ->
onDateSelected(
Calendar
.getInstance()
.apply {
set(year, month, dayOfMonth)
}
.time
)
}
}
)
}
<style name="CalenderViewCustom" parent="ThemeOverlay.MaterialComponents.MaterialCalendar">
<item name="colorAccent">@color/md_theme_dark_tertiary</item>
<item name="colorOnPrimary">@color/md_theme_dark_onPrimary</item>
<item name="colorSurface">@color/md_theme_dark_surface</item>
</style>
<style name="CalenderViewCustom" parent="ThemeOverlay.MaterialComponents.MaterialCalendar">
<item name="colorAccent">@color/md_theme_light_tertiary</item>
<item name="colorOnPrimary">@color/md_theme_light_onPrimary</item>
<item name="colorSurface">@color/md_theme_light_surface</item>
</style>
@KentVu
Copy link

KentVu commented Jun 30, 2023

I got this exception around ContextThemeWrapper(context, R.style.CalenderViewCustom) call. I'm using compose-multiplatform, any ideas?

E  FATAL EXCEPTION: main Process: com.xxxx.debug, PID: 9763 
java.lang.UnsupportedOperationException: Failed to resolve attribute at index 15: TypedValue{t=0x2/d=0x101042a a=1}
	at android.content.res.TypedArray.getColorStateList(TypedArray.java:597)
	at android.widget.DayPickerView.<init>(DayPickerView.java:104)
	at android.widget.CalendarViewMaterialDelegate.<init>(CalendarViewMaterialDelegate.java:35)
	at android.widget.CalendarView.<init>(CalendarView.java:126)
	at android.widget.CalendarView.<init>(CalendarView.java:106)
	at android.widget.CalendarView.<init>(CalendarView.java:101)
	at android.widget.CalendarView.<init>(CalendarView.java:97)
	at com.stackoverflow.CustomCalendarViewKt$CustomCalendarView$1.invoke(CustomCalendarView.kt:30)
	at com.stackoverflow.CustomCalendarViewKt$CustomCalendarView$1.invoke(CustomCalendarView.kt:27)
	at androidx.compose.ui.viewinterop.ViewFactoryHolder.<init>(AndroidView.android.kt:314)

@ArnyminerZ
Copy link
Author

This feature is not supported by Compose Multiplatform. Only on Android. Maybe you can create a workaround with expect and actual functions, but I haven't tested it out. There is no such thing as AndroidView on desktop, for example.

@KentVu
Copy link

KentVu commented Jul 5, 2023

Thank you, I've finally come up with using DatePickerDialog from their newly released Jetpack Compose 2023.06.01 bom.

@ArnyminerZ
Copy link
Author

Awesome!

@himanshusprinto
Copy link

Thank you so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment