Baseflow / Baseflow/flutter-geocoding
[Regression]: Locale silently ignored on Android — toString() emits "zh_TW" but native forLanguageTag() accepts BCP-47 only
- Dominant language
- Dart
- Stars
- 153
- Forks
- 92
- PR merge metrics
- No merged PRs in 30d
Description
### Is there an existing issue for this?
- [x] I have searched the [existing issues](https://github.com/baseflow/flutter-geocoding/issues) — found none covering this.
### Affected platform(s)
- [x] Android
- [ ] iOS
### Old behavior
On `geocoding_android` 4.x, passing a `Locale` with a country subtag worked: the returned placemarks were localized to that locale. The native side converted the identifier manually (`LocaleConverter.java`, splitting the string on `_`), which accepted the underscore form that the Dart side produces.
### Current behavior
On `geocoding_android` 5.x the locale is **silently ignored** — results come back in the device's default language. The call succeeds, no exception, nothing observable at the call site.
Root cause is a mismatch between the producer and the consumer of the identifier string.
Dart side serializes with `toString()`:
```dart
// geocoding_android-5.1.0/lib/src/geocoding_android.dart:164
final Locale? nativeLocale =
locale != null ? Locale(identifier: locale.toString()) : null;
```
Flutter's `Locale.toString()` joins subtags with an **underscore** — `Locale('zh','TW').toString() == "zh_TW"`.
Native side parses with `forLanguageTag()`:
```kotlin
// geocoding_android/android/src/main/kotlin/com/baseflow/geocoding/proxies/LocaleProxyApi.kt:17
return Locale.forLanguageTag(identifier);
```
`Locale.forLanguageTag` accepts BCP-47 only. Per the Javadoc: *"If the specified language tag contains any ill-formed subtags, the first such subtag and all following subtags are ignored."* `zh_TW` is a single ill-formed subtag, so **the entire tag is dropped** and an empty (`und`) Locale is returned.
Verified on JDK 21:
```
forLanguageTag("zh_TW") -> language="" country="" toLanguageTag="und"
forLanguageTag("zh-TW") -> language="zh" country="TW" toLanguageTag="zh-TW"
forLanguageTag("zh_Hant_TW") -> language="" country="" toLanguageTag="und"
forLanguageTag("en_US") -> language="" country="" toLanguageTag="und"
```
Note that `GeocoderProxyApi` passes this non-null but empty Locale straight to `Geocoder(context, locale)`, so it does **not** fall back to the null-locale path — the request goes out with an undetermined locale.
This affects **every** locale carrying a country or script subtag (`en_US`, `pt_BR`, `zh_Hant_TW`, …). A language-only locale such as `Locale('ja')` happens to still work, since `"ja"` is already a well-formed tag.
Side note: the README still documents the identifier format as `[languageCode]_[countryCode]` (underscore) — which is now precisely the form that fails.
### Steps to reproduce
1. On Android, set the device language to something other than the locale you are about to request (e.g. device in English).
2. Call `placemarkFromCoordinates(lat, lng, locale: const Locale('zh', 'TW'))` — any locale with a country subtag reproduces it.
3. Observe the returned `Placemark` fields: they are in the device language, not the requested one. No error is raised.
4. Repeat with `const Locale('zh-TW')` (single subtag, so `toString()` already yields a hyphen) — the result is correctly localized.
### Code sample
Code sample
```dart
// Ignored on Android (serializes to "zh_TW" -> forLanguageTag -> und):
final ignored = await Geocoding().placemarkFromCoordinates(
25.0330, 121.5654,
locale: const Locale('zh', 'TW'),
);
// Honored (serializes to "zh-TW"):
final honored = await Geocoding().placemarkFromCoordinates(
25.0330, 121.5654,
locale: const Locale('zh-TW'),
);
```
### Suggested fix
Serialize with `toLanguageTag()` instead of `toString()` on the Dart side:
```dart
Locale(identifier: locale.toLanguageTag()) // "zh-TW"
```
`toLanguageTag()` emits exactly BCP-47, which is what `forLanguageTag` expects. One line, no native change required.
iOS is unaffected either way — `geocoding_darwin` uses `Locale(identifier:)`, and Foundation parses `"zh_TW"` and `"zh-TW"` identically (verified on macOS: both yield `language=zh region=TW`). So switching to `toLanguageTag()` is safe for both platforms.
### Workaround for users
Pass the tag as a single-subtag Dart `Locale`, so that `toString()` already produces the hyphenated form:
```dart
const Locale('zh-TW') // toString() == "zh-TW" -> honored
const Locale('zh', 'TW') // toString() == "zh_TW" -> silently ignored
```
### Current version
`geocoding: 5.0.0` / `geocoding_android: 5.1.0` (latest at time of writing) / `geocoding_platform_interface: 5.0.0`. Still present on `main`.
### Last version without regression
`geocoding_android: 4.0.1`
Contributor guide
Research direction
Start in geocoding_android-5.1.0/lib/src/geocoding_android.dart:164 and compare the locale identifier with android/src/main/kotlin/com/baseflow/geocoding/proxies/LocaleProxyApi.kt:17. Check the existing package tests, then verify that country and script locales reach the native API as valid BCP-47 tags and that Android localization is preserved without affecting iOS.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, dart, flutter
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100