I'd like to let users select between imperial &amp...
# android-architecture
c
I'd like to let users select between imperial & metric units in my app. How are folks accomplishing this in their own apps? I'm considering
CompositionLocal
since units are usually 4-6 children deep in my composable hierarchy. I'm thinking of storing the setting in my cache (realm), making it available in a viewmodel, collecting the state and making it available via CompositionLocalProvider just above my NavHost. Then I'd pass the viewmodel method reference to update the setting values to my "settings screen" composable. Is that reasonable? Is that easier than passing a UnitsPreference arg a bunch of times? Thanks!
I'd like to let users set this instead of using a localized string. Thats because my app is scuba diving related, and the units aren't always correlated between regions.
s
I just realized this exists https://developer.android.com/reference/android/icu/util/LocaleData#getMeasurementSystem(android.icu.util.ULocale) albeit only on API 28 and onwards. But it looks to be just a read-only thing? I haven't dealt with this before myself, but would it make sense for your storage representation to always just be in metric. And then use that CompositionLocal of yours which is gonna have to be driven by your preferences (perhaps default to the function I reference above?) to only take the metric unit from your VM and convert it last moment before you show it to the UI? The conversion should be quite cheap anyway, so just doing it there might just be good enough. Do you need to have this information inside your VM for some other specific reason? And even if you do, you could always look at the preferences (or Realm of course) at that level, since that should be the SSOT for what unit your user prefers.
c
In the past I used to have my ViewModels in charge of all formatting. This was a big mistake because it requires VMs to have access to Context (there are several disadvantages to this). Now I have a separate formatter class that is called by the Composable at the last moment, just as Stylianos mentioned above. That said, I personally wouldn't use a CompositionLocal here at all. Your formatter requires 2 pieces of input: 1. The value to display: the Composable is probably aware of this value; maybe it even provides this value 2. The unit to display it in: this is a user setting that comes from the "data layer" somewhere so your formatter can observer the change in this value, without the Composable ever being involved.
So the API for your Composable can be
formatter.format(value)
It is the Formatter's responsibility to know what the unit is.
s
a user setting that comes from the "data layer" somewhere so your formatter can observer the change in this value, without the Composable ever being involved
This means that the formatter is still something that lives throughout the entire app's lifecycle and needs to somehow be present at those child composables right? Are you simply creating that at root level and then passing it down to the callers that need it, no matter the depth?
c
Hmm I revisited my code and you are right. CompositionLocal is probably the right choice for this. In my case, the formatting was based on the locale. And instead of "observing" the locale, I just have a simple function like this:
Copy code
@Composable
internal fun LocalDate.formatForSurveyHeader(): String {
    val locale = LocalConfiguration.current.locales[0]
    val formatter = remember(locale) {
        DateTimeFormatter.ofPattern("EEEE d MMMM", locale)
    }
    return this.toJavaLocalDate().format(formatter)
}
So in essence this is indeed using a CompositionLocal (in this case
LocalConfiguration
)
👍 1
c
Thanks for the input folks! I ended up going the CompositionLocal route its worked out nicely. Ended up being about 40 usages to display the value across the app, so I think it definitely spared me a fair few parameters args!
🌟 1
...next up is to initialise that setting based on the user's region. Im thinking of using a locale specific en-US strings resource with a default value set to imperial units