In a Flutter currency converter supporting en, ar and he, how is the app locale resolved, and when should you write localeListResolutionCallback?
answer
- list callback, single callback, default
- null means fall through
- single callback sees only the first
- language-only match loses the country
- iw is already he
basics
~20 sWidgetsApp tries localeListResolutionCallback, then localeResolutionCallback, then basicLocaleListResolution, moving on whenever a callback returns null. Write the list callback only for a product rule the default cannot express; the single callback sees just the first preferred locale.
solid answer
~40 sAt startup and whenever the device's locale list changes, `WidgetsApp` resolves one locale. It first calls `localeListResolutionCallback(preferredLocales, supportedLocales)`; if that returns `null` it calls `localeResolutionCallback` with only the **first** preferred locale; if that also returns `null` it runs `basicLocaleListResolution`. The default walks the user's list and prefers a perfect match, then language plus script, then language plus country, then a language-only match, then a country-only match, and finally `supportedLocales.first`. Write `localeListResolutionCallback` for a rule the default cannot express, such as mapping one unsupported language to a chosen fallback, and prefer it over `localeResolutionCallback`, whose single-locale view breaks users with several languages. Two traps: a language-only match returns your supported `Locale('ar')`, so the user's country is gone, and `Locale('iw')` is already canonicalised to `he`.
code
dart · 18 linesimport 'package:flutter/material.dart';
Locale? resolveConverterLocale(
List<Locale>? preferred,
Iterable<Locale> supported,
) {
for (final locale in preferred ?? const <Locale>[]) {
for (final candidate in supported) {
if (candidate.languageCode == locale.languageCode) {
return candidate;
}
}
}
// No shared language at all: English, whatever the list order says.
return const Locale('en');
}
// MaterialApp(localeListResolutionCallback: resolveConverterLocale, ...)go deeper
Know that Flutter picks the app locale by matching the device's preferred languages against supportedLocales, falling back to the first entry.
Explain the order list callback, single callback, default, what returning null means, and the default algorithm's priorities.
Choose the list callback over the single one, keep region data from the device list, test resolution with simulated locale lists, and remove needless special cases like iw.
Define the product's fallback-language policy per market and decide whether it lives in supportedLocales order, a callback, or an in-app picker.
## The resolution pipeline `WidgetsApp` (and so `MaterialApp`) resolves **one** app locale from the device's ordered list of preferred locales and your `supportedLocales`. It runs at startup and again whenever the platform reports a new locale list. The steps: 1. **`localeListResolutionCallback(List<Locale>? locales, Iterable<Locale> supportedLocales)`**, if set. A non-null result wins. 2. **`localeResolutionCallback(Locale? locale, Iterable<Locale> supportedLocales)`**, if set, called with only the **first** preferred locale. A non-null result wins. 3. **`basicLocaleListResolution`**, the default algorithm. Returning `null` from a callback is the documented way to say "no opinion, continue". When `MaterialApp.locale` is set (an in-app language picker), the same pipeline runs with that single locale as the preferred list. ## What the default algorithm does For each preferred locale in order, `basicLocaleListResolution` looks for: 1. a **perfect match** of language, script and country; 2. a **language plus script** match; 3. a **language plus country** match; 4. a **language-only** match, returned at once for the first preferred locale, or kept while the next preferred locale is checked for a better match; 5. after the list is exhausted, a **country-only** match; 6. otherwise **`supportedLocales.first`**. It does not know about language distance: a Persian speaker is not steered towards Arabic, and a French speaker falls to the first supported locale. ## When a custom callback earns its place - **A product fallback rule.** For example, users whose only languages are unsupported should see English, even if the generated `supportedLocales` list starts with `ar`. Returning `const Locale('en')` when nothing matches expresses that (reordering `supportedLocales` is the lighter alternative). - **Region-aware mapping.** Mapping several regional variants onto the one you ship, when the default's priorities would pick another. - **A remote or saved preference** that must be checked against what the device offers. Prefer **`localeListResolutionCallback`**. `localeResolutionCallback` only sees the first preferred locale, so a user whose list is `[fr, he]` gets French evaluated alone; returning a fallback there skips the Hebrew the user also reads. ## Traps specific to this app | Trap | What happens | Fix | |---|---|---| | Country lost on a language-only match | A device set to `ar_EG` resolves to your `Locale('ar')`, so `Localizations.localeOf` has no `EG` | Read the device list from `PlatformDispatcher.instance.locales` when you need the country, for example to default the source currency | | Writing a callback for `iw` | Unnecessary: `Locale('iw')` has `languageCode` `he`, and the two compare equal | Delete the special case | | Returning a locale not in `supportedLocales` | The delegates are asked to load a locale they may not support, and localized resources go missing | Only return members of `supportedLocales` | | Slow or async logic | Callbacks are synchronous and cannot await a stored preference | Resolve saved preferences before `runApp`, then use `MaterialApp.locale` | ## An in-app language picker Many currency apps let users override the phone's language. The robust shape does not need a resolution callback at all: 1. Store the user's choice (for example `he`) with your persistence layer and read it before `runApp`. 2. Pass it as `MaterialApp.locale`; leave it `null` to follow the device. 3. Rebuild `MaterialApp` with the new value when the user changes the setting; the pipeline resolves that single locale against `supportedLocales`, and `Localizations` reloads the delegates. A callback that reads the stored choice works too, but it mixes a user setting into device matching and cannot wait for storage, so the explicit `locale` property is easier to reason about and to test. ## Keeping formatting in step Whatever the pipeline returns is what `Localizations.localeOf(context)` reports, so pass that value to `NumberFormat` and `DateFormat`. If you chose to show English to French users, amounts should also be formatted in English, not in the device's French conventions. ## Testing Resolution is a pure function of its inputs, so it tests well without a device: - call your callback directly with lists such as `[Locale('fr'), Locale('he')]` and assert the result; - pump `MaterialApp` in a widget test with `locale:` set and assert `Localizations.localeOf`; - use `tester.platformDispatcher.localesTestValue` to simulate a device language list end to end.
- A Hebrew user's device list is [fr_FR, he_IL]; what does a localeResolutionCallback that returns supportedLocales.first on no match do to them?It receives only `fr_FR`, finds no match and returns the first supported locale, so the Hebrew in second place is never considered. With no callback, or with `localeListResolutionCallback`, the default algorithm would continue to `he_IL` and pick Hebrew.
- How do you know the user is in Egypt when the app resolved to plain ar?The resolved locale is your supported `Locale('ar')`, which has no country. Read `PlatformDispatcher.instance.locales` (or `WidgetsBinding.instance.platformDispatcher.locales`) for the device's full list, including `ar_EG`, and use that only for region defaults such as the home currency.
- Do you need a special case for devices that report Hebrew as iw?No. `Locale` canonicalises the deprecated `iw` subtag to `he`, so `Locale('iw')` and `Locale('he')` are equal and matching works without extra code.
saying these in an interview costs you the question
- localeResolutionCallback receives the full list of preferred locales.
- The default algorithm picks the closest related language, such as Arabic for Persian.
- Returning null from a resolution callback leaves the app without a locale.
- Hebrew devices reporting iw need a custom mapping callback.
- The resolved locale always keeps the device's country code.