Recently I came across a specific requirement in my view system project. I was customising theme in my app dynamically in runtime. This dynamic colors can be applied everywhere in my app except for one thing.
Top App Bar…. I was just kidding. You guessed it right. Date Picker.
I started to look for various projects in GitHub for these requirement. Then I became curious, “How hard is it to create it in compose, locale support and all?”. And that’s what prompted me to this.
A couple of disclaimers before going into the code.
- I know there are material date picker in compose, that looks and feels nice. But the UI in that is a bit different from that of traditional date picker. Since the users are accustomed to the age old UI ( and to satisfy my curiosity), I decided to create this.
- I did not use desugaring. That is because of this issue. Basically the gist of the entire issue is that desugaring is not supported in library and I would like to use this code as a library in future.
Now we can start from the top.
The UI is as simple as it looks. Just two text composables that changes the state.
The year should be formatted specific to locale. We can use
NumberFormat for that. (Remember to replace any comma or special character from the formatted string.)
The text in the month is the tricky one. Remember we should also handle different locale.
I have scoured the android open source code and there is a way to do the entire date picker without any API level restrictions in that open source code. Unfortunately, we cannot use that code. So I have used an amalgamation of native code that we can use and API’s from our dear old Calendar object in places we can’t accomplish this.
I have used java.icu.text.DateFormat not to be confused with java.text.DateFormat.
The header part is over. Now the main content.
The main content will of course be inside Horizontal pager. The item count of pager is the number of months to be shown in between start and end years. Following this, the current index position can also be found pretty easily.
Now lets do the month header.
Yes again, the UI is as simple as it looks. We could do it easily. But the text is the tricky one. Fortunately we have simple solution for this.
We can use YearMonth API to get the display name for any device on or above OREO. For others we can use the calendar API.
The icons on the side also should be handled for RTL locale, simply rotate it 180 degree if locale is RTL.
Next is the week days. Yes, you guessed it right. The UI is simple but locale handling gets a bit tricky. Fortunately we could get the first day of week from each locale from Calendar API.
From that, getting the single lettered week text is just as we got the value for month text.
But now comes the hardest of them all. Dates.
The grid of days in each month should start at the week day correctly and also be aligned correctly.
The total days of each month, the first day of week, the week day of the first day of month all can be retrieved from Calendar API.
The UI of rows and columns can be easily achieved.
The day index where the month has to start can be calculated easily by having a list of week days in the order of that locale and getting the index of the first day of the month.
If that sentence is confusing, don’t worry here is the code.
Now since we know when the month is going to start and end, we can get the locale formatted text for values and empty string for others.
Now the hard part is over. Next is dialog buttons (OK and Cancel).
The UI is simple…. blah blah blah, you get the gist of it. We need locale specific text.
Luckily we don’t need to write strings.xml for each and every language for OK and Cancel.
We can find it ourselves by getting
Next is the final part I promise. It is the year picker.
Now I would say UI is simple, locale handling gets tricky, but as far as I saw there is no locale handling for year picker, So you may add locale handling to year picker using the number format object if you want. But I am just going to add a Lazy Column with default numbers. Also don’t forget the separator at last.
Ah, but no part of date picker is without complication. We need to put that lazy column the same height as month picker. Easiest way to achieve this is static height for both. But call me lazy, I don’t want to check every android device in the world to see whether my UI breaks in any of that by doing the easy way. So I decided to go by the next best approach.
I got the height by getting it from
onGloballyPositioned modifier and storing it in a state.
Note: For any other component, we can just use IntrinsicSize for this exact requirement. But IntrinsicSize is not allowed for lazy column.
So this is it. We have completed the date picker.
Any kind of feedback is highly appreciated.
GitHub Repo: https://github.com/V-Abhilash-1999/ComposeDatePicker