locale_money_field 0.1.0
locale_money_field: ^0.1.0 copied to clipboard
Locale-aware money and number input for Flutter with a precision-safe value model and a significant-character caret that never jumps on mid-string edits.
locale_money_field #
Locale-aware money and number input for Flutter, with a precision-safe value model and a significant-character caret that never jumps on mid-string edits.
Type 12345 in en-US and you get $12,345; switch the locale to de-DE and
the same keystrokes give 12.345 €; in hi-IN they group as ₹12,345 with the
Indian lakh system; in ar-EG they render in Arabic-Indic digits. Grouping
separators appear as you type, the caret stays put when you edit in the middle,
and backspacing a separator removes the digit in front of it.
| iOS | Android |
|---|---|
![]() |
![]() |
Features #
- Locale-aware formatting powered by CLDR data from
package:intl: grouping, decimal marks, digit shapes (Latin, Arabic-Indic), and currency symbol placement all follow the locale. - Precision-safe value model. Amounts are an exact
BigIntcount of minor units plus a scale, never adouble, so they stay correct even on the web whereintis a 53-bit float. - Significant-character caret. The cursor tracks the digit you were editing instead of snapping to the end, even when grouping shifts the surrounding text.
- Smart separator backspace. Deleting a grouping separator removes the digit before it, matching what users expect.
- Live bounds. Optional inclusive
min/maxreject out-of-range edits as they happen. - Layered, extensible API. Use the batteries-included
MoneyFieldwidget, drop aMoneyInputFormatteronto a plainTextField, or call the pureformatMoney/parseMoneyfunctions directly. Swap in your ownMoneyFormatterto change behaviour without forking.
Getting started #
Add the dependency:
dependencies:
locale_money_field: ^0.1.0
Then import it:
import 'package:locale_money_field/locale_money_field.dart';
Usage #
The MoneyField widget #
For the common case, give it a config and listen with onChanged. The parsed
amount arrives as a MoneyValue? (null when the field is empty):
MoneyField(
config: const MoneyConfig(locale: 'de-DE', currency: 'EUR'),
decoration: const InputDecoration(
labelText: 'Amount',
border: OutlineInputBorder(),
),
onChanged: (value) => print(value?.decimalString), // canonical "1234.56"
)
Reading and setting the value with a controller #
MoneyEditingController is a TextEditingController that understands money. It
keeps the text formatted for its config and exposes the parsed amount as
moneyValue:
final controller = MoneyEditingController(
config: const MoneyConfig(locale: 'en-US', currency: 'USD'),
);
// Set the field programmatically.
controller.moneyValue = MoneyValue.fromDecimalString('1234.56'); // shows $1,234.56
// Read what the user entered.
final amount = controller.moneyValue; // MoneyValue? or null when empty
// Reformat live by swapping the locale / currency.
controller.config = const MoneyConfig(locale: 'de-DE', currency: 'EUR');
MoneyField(controller: controller);
Dropping a formatter onto a plain TextField #
When you want full control over the field, use MoneyInputFormatter directly:
TextField(
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
MoneyInputFormatter(config: const MoneyConfig(currency: 'USD')),
],
)
Pure formatting and parsing, no widgets #
The engine is available as plain functions for validation, display, or tests:
const config = MoneyConfig(locale: 'hi-IN', currency: 'INR');
final text = formatMoney(MoneyValue.fromDecimalString('1234567.89'), config);
// "₹12,34,567.89"
final value = parseMoney('₹12,34,567.89', config);
// MoneyValue(minorUnits: 123456789, scale: 2)
The MoneyValue model #
MoneyValue is the exact, locale-independent amount that flows in and out of the
field:
final a = MoneyValue.fromDecimalString('19.99'); // minorUnits 1999, scale 2
final b = MoneyValue.fromMinorUnits(BigInt.from(2500), scale: 2); // 25.00
print(a.decimalString); // "19.99" (always '.' decimal, no grouping)
print(a < b); // true
print(a == MoneyValue.fromDecimalString('19.990')); // true (trailing zeros ignored)
Store and transmit the decimalString (or minorUnits + scale); never round
a money value through double.
Configuration #
MoneyConfig is immutable and every field is optional:
| Field | Default | Purpose |
|---|---|---|
locale |
ambient locale | BCP-47 / ICU locale id, e.g. en-US, de-DE, ar. |
currency |
none | ISO 4217 code, e.g. USD. Null means plain number mode. |
fractionDigits |
currency default | Force a fixed number of fraction digits. |
allowNegative |
true |
Accept a leading sign and negative values. |
min / max |
none | Inclusive bounds; out-of-range edits are rejected. |
symbolPosition |
locale default | Override SymbolPosition.prefix / suffix. |
groupingEnabled |
true |
Toggle grouping separators. |
currencySymbol |
locale default | Override the symbol glyph. |
const MoneyConfig(
locale: 'en-US',
currency: 'USD',
max: null, // pass a MoneyValue to cap input
groupingEnabled: true,
)
Extending #
Behaviour is pluggable through the MoneyFormatter interface. The default
IntlMoneyFormatter is backed by package:intl; implement your own to change
formatting, parsing, or edit handling, and pass it to any of the field, the
controller, or the formatter:
class MyFormatter implements MoneyFormatter {
// format / parse / applyEdit ...
}
MoneyField(formatter: const MyFormatter());
Additional information #
Issues and pull requests are welcome at
https://github.com/NadeemIqbal/locale_money_field. See the example/ app for a
runnable demo that types across several locales.


