I'd like to be able to display the value entered in TextFormField with NumberFormat('###,##0.00', 'en_US').
There are no issues in the conversion, but I'm having issues displaying the value of formattedPrice in the TextFormField. I'd also like to set the default value of TextFormField as '0.00'. In my sample code below, the TextFormField restricts input to only have a single decimal symbol/separator and two decimal values.
I'm fine with .00 decimals not displayed when there are no decimals present in the TextFormField value during input, but I'd like the two decimal places format be placed when the value is entered.
| TextFormField value displayed when typing | TextFormField value displayed when entered |
|---|---|
| 123 | 123.00 |
| 123.5 | 123.50 |
| 123.54 | 123.54 |
| 12,345 | 12,345.00 |
| 12,345.6 | 12,345.60 |
Minimal Repro
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _subscriptionFormKey = GlobalKey<FormState>();
final _subscriptionPriceController = TextEditingController();
NumberFormat numFormat = NumberFormat('###,###.00', 'en_US');
NumberFormat numSanitizedFormat = NumberFormat('en_US');
var currency = 'USD';
void _validate() {
if (_subscriptionFormKey.currentState!.validate()) {}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Form(
key: _subscriptionFormKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
autofocus: true,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}')),
],
keyboardType: TextInputType.numberWithOptions(decimal: true),
validator: (cost) {
if (cost == null || cost.isEmpty) {
return 'Empty';
}
return null;
},
textInputAction: TextInputAction.next,
/// TODO TextFormField default value
/// I'd like to be to set '0.00' value by default
controller: _subscriptionPriceController,
decoration: InputDecoration(
hintText: 'Price',
prefixText:
NumberFormat.simpleCurrency(name: currency).currencySymbol,
),
onChanged: (price) {
/// TODO Display [formattedPrice] in TextFormField
var formattedPrice = numFormat.format(double.parse(price));
debugPrint('Formatted $formattedPrice');
var numSanitized = numSanitizedFormat.parse(price);
debugPrint('Sanitized: $numSanitized');
_subscriptionPriceController.value = TextEditingValue(
text: price,
selection: TextSelection.collapsed(offset: price.length),
);
},
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _validate,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Update:
Thanks to @Andrej for suggesting to use onFieldSubmitted() instead of onChange(). I've updated my code with the fix included. I hope this will help other developers blocked with similar issue.
Code with the fix:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _subscriptionFormKey = GlobalKey<FormState>();
final _subscriptionPriceController = TextEditingController();
final NumberFormat numFormat = NumberFormat('###,##0.00', 'en_US');
final NumberFormat numSanitizedFormat = NumberFormat('en_US');
var currency = 'USD';
var defaultPrice = '0.00';
void _validate() {
if (_subscriptionFormKey.currentState!.validate()) {}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Form(
key: _subscriptionFormKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
autofocus: true,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}')),
],
keyboardType: TextInputType.numberWithOptions(decimal: true),
validator: (cost) {
if (cost == null || cost.isEmpty) {
return 'Empty';
}
return null;
},
textInputAction: TextInputAction.next,
/// Set TextFormField default value
controller: _subscriptionPriceController..text = defaultPrice,
decoration: InputDecoration(
hintText: 'Price',
prefixText:
NumberFormat.simpleCurrency(name: currency).currencySymbol,
),
onTap: () {
var textFieldNum = _subscriptionPriceController.value.text;
var numSanitized = numSanitizedFormat.parse(textFieldNum);
_subscriptionPriceController.value = TextEditingValue(
/// Clear if TextFormField value is 0
text: numSanitized == 0 ? '' : '$numSanitized',
selection:
TextSelection.collapsed(offset: '$numSanitized'.length),
);
},
onFieldSubmitted: (price) {
/// Set value to 0 if TextFormField value is empty
if (price == '') price = '0';
final formattedPrice = numFormat.format(double.parse(price));
debugPrint('Formatted $formattedPrice');
_subscriptionPriceController.value = TextEditingValue(
text: formattedPrice,
selection:
TextSelection.collapsed(offset: formattedPrice.length),
);
},
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _validate,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
