Repository navigation
Conversation
Code Coverage
Files
|
| val groupingSize = if (decimalFormat != null && decimalFormat.isGroupingUsed) { | ||
| decimalFormat.groupingSize | ||
| } else { | ||
| 3 |
There was a problem hiding this comment.
When the locale does not use grouping we fall back to 3, so we would still group the digits. The KDoc says <= 0 disables grouping, so shouldn't we return 0 here?
| 3 | |
| 0 |
There was a problem hiding this comment.
Good catch! Updated NumberFormatSymbols.fromLocale so that when decimalFormat == null || !decimalFormat.isGroupingUsed, groupingSize is set to 0 (disabling grouping) rather than falling back to 3. Also added a unit test verifying that NumberFormatSymbols.fromLocale returns groupingSize = 0 when isGroupingUsed == false.
| override val unitSystem: UnitSystem = UnitSystem.METRIC | ||
|
|
||
| override fun toDistanceUnits(meters: Meters): ValueWithUnitsTemplate = | ||
| if (abs(meters.value) < KILOMETERS_THRESHOLD.value) { |
There was a problem hiding this comment.
There is an edge case right below the threshold: 999.6 m stays in the meters branch but rounds up when formatted, so we get "1,000 m" instead of "1.0 km" (same for 1,319.6 ft, which shows as "1,320 ft" instead of "0.3 mi"). We already handle this for the 10 km/mi switch with isUnderTenAtOneDecimal. Could we compare the rounded value here too, and add a test near the threshold?
There was a problem hiding this comment.
Great catch on the boundary rounding! Updated both MetricUnitsConverter.toDistanceString and ImperialUnitsConverter.toDistanceString to compare the rounded integer value in small units (abs(metersValue).roundToLong() < METERS_PER_KILOMETER and abs(feet).roundToLong() < THRESHOLD_FEET, clamping the miles magnitude to at least 0.25 once rounded feet reach 1,320 ft). Now 999.6.meters formats as "1.0 km" instead of "1,000 m", and 1_319.6.feet formats as "0.3 mi" instead of "1,320 ft". Added unit tests for both boundaries in UnitsTest and UnitsJavaTest.
| Distance or elevation in feet, formatted to 0 decimal places. | ||
| For example, 1234.56 feet will be formatted as "1,235 ft". | ||
| --> | ||
| <string name="amu_in_feet" translatable="false">%1$,.0f ft</string> |
There was a problem hiding this comment.
Just to understand it: since all the templates are translatable="false", the Resources overloads produce the same output as the pure Kotlin path (formatWithTemplate), and we end up with two code paths to keep in sync. Should we either make the unit labels translatable, or drop the Resources overloads and the resource IDs from the public API?
There was a problem hiding this comment.
Great observation! We went with the first option (making the unit templates translatable while unifying onto a single number-formatting code path):
- Translatable
%1$sUnit Templates (amu_in_feet,amu_in_miles,amu_in_meters,amu_in_kilometers): Removedtranslatable="false", switched the placeholders from%1$,.0f/%1$,.1fto%1$s(cutting the resource count from 6 to 4 by droppingamu_in_miles_wholeandamu_in_kilometers_whole), and added 24 localizedvalues-<locale>/strings.xmltranslations (e.g.,ja,ko,zh-rCN,zh-rTW,ru,uk,ar,fr,hi,th,el,iw, etc.). - Single Number-Formatting Code Path: Both the pure-Kotlin/KMP
NumberFormatSymbolspath and the AndroidResourcespath now share the exact sameformatDistance/NumberFormatSymbolsrounding and grouping logic (ValueWithUnitsTemplateandString.formatare gone), and callers can also compose or customize converters viaUnitsConverter(...)/UnitsConverter.create(...).
| */ | ||
| public object ImperialUnitsConverter : UnitsConverter() { | ||
| /** Threshold (`0.25` miles = `1,320` feet) at which imperial distances switch from feet to miles. */ | ||
| public val MILES_THRESHOLD: Meters = 0.25.miles |
There was a problem hiding this comment.
Since Meters is a value class, the getter gets mangled (getMILES_THRESHOLD-NA-LpbA in library.api) and can't be called from Java. Could we add @get:JvmName here and on KILOMETERS_THRESHOLD, or expose them as Double constants?
There was a problem hiding this comment.
Added @get:JvmName("getMilesThreshold") on ImperialUnitsConverter.MILES_THRESHOLD and @get:JvmName("getKilometersThreshold") on MetricUnitsConverter.KILOMETERS_THRESHOLD so Java callers can invoke getMilesThreshold() and getKilometersThreshold() without inline value class mangling (verified in library.api and UnitsJavaTest).
|
|
||
| /** Shorthand to create a [Meters] measurement from a [Number] of meters. */ | ||
| @get:JvmName("m") | ||
| public inline val Number.m: Meters |
There was a problem hiding this comment.
I wonder if we could drop the short aliases (m, ft, mi, km). Being these extensions on Number in our root package, anyone importing them gets 5.m in autocompletion, and m is quite likely to clash with other libraries. The long forms read well enough, what do you think?
There was a problem hiding this comment.
Agreed — since com.google.maps.android is the root package, single/two-letter extensions (m, km, ft, mi) on Number are too prone to autocomplete noise and collisions. Removed .m, .km, .ft, and .mi and kept the explicit .meters, .kilometers, .feet, and .miles extensions.
|
|
||
| /** Returns the equivalent number of international feet for this [Meters] measurement. */ | ||
| @get:JvmName("toFeet") | ||
| public inline val Meters.toFeet: Double |
There was a problem hiding this comment.
nit: in Kotlin to* names are usually functions, so meters.toFeet reads a bit like it is missing the (). What about inFeet / inMiles / inKilometers, similar to how Duration exposes its values?
There was a problem hiding this comment.
Renamed toMeters, toKilometers, toFeet, and toMiles to inMeters, inKilometers, inFeet, and inMiles (including @get:JvmName("inMeters"), etc. for Java static imports) to match Kotlin's Duration naming convention (inWholeMeters / toDouble(DurationUnit)).
| * and elevations: United States (`"US"`), Liberia (`"LR"`), Myanmar (`"MM"`), and | ||
| * United Kingdom (`"GB"`). | ||
| */ | ||
| public val IMPERIAL_COUNTRY_CODES: Set<String> = setOf("US", "LR", "MM", "GB") |
There was a problem hiding this comment.
Out of curiosity, is GB meant to apply to elevations too? In the UK road distances are in miles, but elevations (e.g. Ordnance Survey maps) are in meters, so "14,440 ft" may look odd there. Not a blocker, but maybe worth documenting, or splitting the distance and elevation systems later.
There was a problem hiding this comment.
Great point! Updated UnitSystem (distanceFromLocale / elevationFromLocale) and getUnitsConverter so that "GB" (Locale.UK) defaults out-of-the-box to Imperial road distances (ft / mi) and Metric elevations (m), and added simple ways to override either distance or elevation (converter.withDistanceUnits(...), converter.withElevationUnits(...), getUnitsConverter(locale, distanceUnits = ..., elevationUnits = ...), and UnitsConverter(distanceUnits, elevationUnits)).
…nits
- Default `groupingSize` to `0` in `NumberFormatSymbols.fromLocale` when `!decimalFormat.isGroupingUsed`
- Fix threshold boundary rounding in `MetricUnitsConverter` (`999.6 m` -> `"1.0 km"`) and `ImperialUnitsConverter` (`1,319.6 ft` -> `"0.3 mi"`)
- Replace `translatable="false"` numeric templates with 4 translatable `%1$s` unit templates (`amu_in_feet`, `amu_in_miles`, `amu_in_meters`, `amu_in_kilometers`), add 24 localized `values-<locale>/strings.xml` translations, and unify `Locale` and `Resources` formatting onto a single `NumberFormatSymbols` code path
- Add `@get:JvmName("getMilesThreshold")` and `@get:JvmName("getKilometersThreshold")` for Java interop
- Remove short `Number.m`, `.km`, `.ft`, and `.mi` extensions and rename `Meters.to*` properties to `inMeters`, `inKilometers`, `inFeet`, and `inMiles`
- Default `"GB"` (`Locale.UK`) out-of-the-box to Imperial road distances (`ft`/`mi`) + Metric elevations (`m`), and add `withDistanceUnits`, `withElevationUnits`, `withDistanceConverter`, `withElevationConverter`, `withDistanceFormatter`, `withElevationFormatter`, and `UnitsConverter(...)` / `UnitsConverter.create(...)` overloads
- Update `README.md`, `llm-integration-prompt.md`, `.gemini/skills/android-maps-utils/SKILL.md`, and unit tests (`UnitsTest`, `UnitsJavaTest`, `UnitsDemoTest`)
|
I am seeking to have a good general purpose distance / elevation formatting tool that we can use in the scale bar fix. |
…esholds for ScaleBar - Add `trimTrailingZeros: Boolean = false` and `NumberFormatSymbols.COMPACT` (`groupingSize = 0`, `trimTrailingZeros = true`) for compact widgets like `ScaleBar` - Add `ImperialUnitsConverter(milesThreshold = ...)` / `ImperialUnitsConverter.withMilesThreshold(...)` and `MetricUnitsConverter(kilometersThreshold = ...)` / `MetricUnitsConverter.withKilometersThreshold(...)` - Add `ScaleBar` distance formatting tests matching `googlemaps/android-maps-compose#1045` (`ScaleBarDistanceTest` and `ScaleBarRenderingTests`) - Update `library.api` and documentation examples
Summary
Adds a public
UnitsAPI (com.google.maps.android) to:libraryfor type-safe distance and elevation representation, framework-agnostic unit system resolution, and KMP-compatible logical precision formatting.Key Additions
METERS_PER_FOOT = 0.3048,FEET_PER_METER = 1.0 / METERS_PER_FOOT) and statute mile (FEET_PER_MILE = 5_280,METERS_PER_MILE = 1609.344,MILES_PER_METER) constants alongsideMETERS_PER_KILOMETER = 1_000.MetersInline Value Class:@JvmInline value class Meters(val value: Double) : Comparable<Meters>with+,-,*,/, unary-,Numberconstruction extensions (.meters/.m,.kilometers/.km,.feet/.ft,.miles/.mi), and scalar conversions (.toMeters,.toKilometers,.toFeet,.toMiles).UnitSystem&NumberFormatSymbols:UnitSystem.METRICandUnitSystem.IMPERIALwithfromLocale(locale = Locale.getDefault())andfromCountryCode(countryCode)defaulting"US","LR","MM", and"GB"toIMPERIALand all others toMETRIC.NumberFormatSymbolsfor KMP-friendly locale grouping and decimal separator resolution without relying onjava.util.Formatter(String.format).UnitsConverter,MetricUnitsConverter,ImperialUnitsConverter):fttomiat0.25miles (1,320feet) and frommtokmat1,000meters (1.0km).mbelow1 km,ftbelow0.25 mi, and all elevations) as whole integers (0decimals, with locale thousands grouping, e.g.,"450 m","528 ft","14,440 ft").0.25to< 10mi,1.0to< 10km) with1decimal digit, keeping the trailing.0(e.g.,"0.3 mi","1.0 km","2.0 km","2.5 mi").>= 10km or>= 10mi) as whole integers (0decimals, e.g.,"10 km","12 mi").Locale/NumberFormatSymbols) and AndroidResourcesformatting (amu_in_*), plusDoubleoverloads for Java callers.UnitsDemoActivity(Kotlin + Compose + GoogleMap) and updatedDistanceDemoActivity(Java).UnitsTestandUnitsDemoTest.Test Plan
./gradlew apiCheck spotlessCheck :library:testDebugUnitTest :demo:testStandardDebugUnitTest :library:lintDebugUnitsDemoActivityandDistanceDemoActivityon emulator (emulator-5554) and Pixel 6.