33

I'm working on an app that wants to display lengths either in centimeters (cm) or in inches("). Is there a way to select the right unit from the locale? In any event I'm also going to put in an option so that the user can to override the locale setting.

USA, Liberia, and Burma should use imperial units and the rest of the world normal units. One way is to put in this logic in my own classes, but I would prefer using any built in logic if available. Any pointers?

razlebe
  • 7,134
  • 6
  • 42
  • 57
vidstige
  • 12,492
  • 9
  • 66
  • 110

6 Answers6

53

In the end I went for the following solution.

public class UnitLocale {
    public static UnitLocale Imperial = new UnitLocale();
    public static UnitLocale Metric = new UnitLocale();

    public static UnitLocale getDefault() {
            return getFrom(Locale.getDefault());
    }
    public static UnitLocale getFrom(Locale locale) {
        String countryCode = locale.getCountry();
        if ("US".equals(countryCode)) return Imperial; // USA
        if ("LR".equals(countryCode)) return Imperial; // Liberia
        if ("MM".equals(countryCode)) return Imperial; // Myanmar
        return Metric;
    }
}

Use it like this for example.

if (UnitLocale.getDefault() == UnitLocale.Imperial) convertToimperial();

If convert methods are also need they can preferably be added to subclasses of UnitLocale. I only needed to detect wheter to use imperial units and send it to the server.

Using ints over java objects have extremely slim performance gains and makes the code harder to read. Comparing two references in java is comparable in speed to comparing two ints. Also using objects allow us to add methods to the UnitLocale class or subclasses such as, convertToMetric, etc.

You could also use an enum instead if you prefer that.

shim
  • 9,289
  • 12
  • 69
  • 108
vidstige
  • 12,492
  • 9
  • 66
  • 110
  • 3
    Nice solution@. I just changed it to use enum :) so I can use it in an utility class I already have for unit conversion. – fr4gus Oct 11 '12 at 05:41
  • @MarcinOrlowski thanks for upvote, but whats the point with the int? – vidstige Jun 06 '13 at 21:01
  • Nice Answer! What is the basis for only those 3 countries to be in Imperial? – Prakash Nadar Jun 25 '13 at 00:57
  • thanks! Hard to tell really, but in my view it mostly historical and political reasons for keeping the imperial system. – vidstige Jun 25 '13 at 08:43
  • FYI, Imperial units are not the same as United States units. For instance, an Imperial Gallon is 16% larger than a US Gallon, and an Imperial Pound is 21% larger than a US Pound. – AJMansfield Jan 16 '14 at 14:33
  • 1
    from API Level 28, the Android framework provides an official API for this. Read my response below. – Lorenzo Petroli Apr 16 '20 at 04:44
14

LocaleData.getMeasurementSystem is available from API level 28 and beyond. It returns the information you are looking for.

Lorenzo Petroli
  • 458
  • 6
  • 14
9

A more or less complete way to do this is this way.

Kotlin:

private fun Locale.toUnitSystem() =
    when (country.toUpperCase()) {
        // https://en.wikipedia.org/wiki/United_States_customary_units
        // https://en.wikipedia.org/wiki/Imperial_units
        "US" -> UnitSystem.IMPERIAL_US
        // UK, Myanmar, Liberia, 
        "GB", "MM", "LR" -> UnitSystem.IMPERIAL
        else -> UnitSystem.METRIC
    }

Note that there is a difference between UK and US imperial systems, see the wiki articles for more details.

joecks
  • 4,539
  • 37
  • 48
  • 2
    Where does the `UnitSystem` type come from? – stkent Sep 18 '19 at 17:32
  • 1
    Also for rough comparison US customary vs. imperial see: https://en.wikipedia.org/wiki/Comparison_of_the_imperial_and_US_customary_measurement_systems – dominik Jan 03 '20 at 13:07
9

Building on the other nice solutions here, you can also implement this as a Kotlin extension function to the Locale object:

fun Locale.isMetric(): Boolean {
    return when (country.toUpperCase(this)) {
        "US", "LR", "MM" -> false
        else -> true
    }
}

This way, all you need to do is call:

val metric = Locale.getDefault().isMetric()
Asaf Pinhassi
  • 15,177
  • 12
  • 106
  • 130
Russell Stewart
  • 1,960
  • 4
  • 24
  • 31
5
  1. Programmatically

Making a small improvement in the solution from @vidstige

I would use getCountry().toUpperCase() to be safe and change the checks to a switch for a cleaner code. Something like this:

public static UnitLocale getFrom(Locale locale) {
    String countryCode = locale.getCountry().toUpperCase();
    switch (countryCode) {
        case "US":
        case "LR":
        case "MM":
            return Imperial;
        default:
            return Metric;
    }
}
  1. From Resources

Another solution could be creating resources folders for each country like: [values_US] [values_LR] [values_MM] with a boolean resource changed to true. Then read that boolean resource from the code.

Benny
  • 1,650
  • 17
  • 20
  • When is the `toUpperCase()` needed? It is unused code and should be removed. How is a switch case cleaner? It's just more lines of code and messier to reda. – vidstige Nov 22 '16 at 06:33
  • 2
    Hi, We are know discussing different coding styles. I believe a switch is actually more efficient than several, if else statement. – Benny Dec 01 '16 at 16:47
  • 1
    About the `toUpperCase()`, I know it looks redundant, but Android devices manufacturers can modify the source code and you never know if the country code will be always upper case. This is a matter of taste. – Benny Dec 01 '16 at 16:49
  • 1
    Yes, and it's very important to be able to motivate your coding styles imho. If you do `toUpperCase()` where do you stop? Manufaturers can also add prefixes and suffixes. Perhaps you should also trim whitespace? – vidstige Dec 01 '16 at 16:54
2

Just give the user the option to choose a preferred unit in a settings menu. If it is a traveling user you don't want the app to be geographically aware, IMO.

AndroidHustle
  • 1,794
  • 5
  • 24
  • 47
  • Good point. Having this as an option is always better than having it selected for me. – alex Feb 04 '11 at 13:10
  • 6
    Good point. I will put in a setting anyway. However the locale is dependent of the user and not the location. That is, a user from USA would always want to see inches regardless of where she is traveling. Therefor a nice default-selection would be convenient. – vidstige Feb 04 '11 at 13:20
  • ok I see.. Maybe you could use the ACTION_GET_LANGUAGE_DETAILS from RecognizerIntent and from there draw conclusions on what unit the user would prefer from what language they have set as default... This however will not work for users having English as default language and and still prefer the metric system... Good luck. – AndroidHustle Feb 04 '11 at 13:31
  • 5
    Travelling has nothing to do with the locale. An American can choose a LR or MM locale without travelling. – Prakash Nadar Jun 25 '13 at 00:56
  • 2
    In my case, I do have a settings to set the units, but I want to use this auto-detection to set the default values. – Corbella Jun 17 '14 at 13:47
  • 1
    @Corbella's way is the right way to approach this- Then most users can just pick up and use the app without changing anything, but the ability is there if they need it. – Peter Johnson May 10 '15 at 13:18