Search code examples
flutterformattingintl

How to format user input double in TextField as and when being entered by user in Flutter?


I'm trying to display the user input double (may be without decimal dot and that follows) number as a formatted value e.g. 123456789012.1234 as 12345,67,89,012.1234 using not the western numbering system, but using Indian Subcontinent numbering system as shown. Preferably, no commas after crore.

With the intl package, I could display formatted output, but I also need to format the input being entered by the user in the text input filed. And this I could not manage to get working.

The minimal code:

I've written the following minimal code to show my problem. Here, as I enter 123 an then 4 a comma appears at the correct place (i.e. 1,234) but then when I enter the fifth digit i.e. 5 in succession, only 0 is displayed which is erroneous.

How can I fix this?

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

final _controller = TextEditingController();

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('intl Formatter With Formatted Text Field'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(20.0),
          child: TextField(
            controller: _controller,
            keyboardType: TextInputType.number,
            onChanged: (value) {
              NumberFormat numberFormat = NumberFormat("#,##,##,###.########");
              final formattedValue =
                  numberFormat.format(double.tryParse(value) ?? 0.0);
              _controller.value = TextEditingValue(
                text: formattedValue,
              );
            },
          ),
        ),
      ),
    );
  }
}

Solution

  • What I needed was to format the input double value in TextField to be shown in Indian Numbering Format. In the question I posted my minimal code which tried to do so incompletely. With the help form the answer from @frankenapps here, and my trial and errors, I have finally managed to get what I wanted.

    More suggestions to improve the code or completely new answer are welcome.

    The Final Code:

    import 'package:flutter/material.dart';
    import 'package:intl/intl.dart';
    
    final _controller = TextEditingController();
    
    void main() => runApp(MyApp());
    
    class RememberMyValue {
      static String value_previous_or_this_instance = "";
    }
    
    class MyApp extends StatelessWidget {
      String parseMyNumber(String str) {
        // Function to parse only positive numbers (without any prefix):
        RegExp myNumber01 = RegExp(r'^[0-9]+$');
        RegExp myNumber02 = RegExp(r'^[0-9]+\.$');
        RegExp myNumber03 = RegExp(r'^[0-9]+\.[0-9]+$');
    
        NumberFormat numberFormat01_and_02 = NumberFormat("#,##,##,###");
        // The result of eight #s after the decimal point is that users will be able
        // to input only eight digits after decimal.
        NumberFormat numberFormat03 = NumberFormat("#,##,##,###.########");
        // We want to stop taking digits after 8 digits succeding the decimal dot
        // in the user input in consistent with the numberFormat03 formatting. We
        // declare the following constant for that.
        const int maxDigitsAfterDecimalPeroid = 8;
        const int countOfAPeriod = 1;
    
        print("########## str=$str");
        String lastChar = "";
        String extractedString = "";
    
        int digitsBeforePeriod = str.indexOf(".");
        if (str.length >
            digitsBeforePeriod + countOfAPeriod + maxDigitsAfterDecimalPeroid) {
          extractedString = str.substring(
              0, str.indexOf(".") + (maxDigitsAfterDecimalPeroid + 1));
          str = extractedString;
        }
    
        print("########## extracted_string=$extractedString");
    
        if (str.isEmpty) {
          return "";
        }
    
        if (myNumber01.hasMatch(str)) {
          return numberFormat01_and_02.format(int.parse(str));
        } else {
          if (myNumber02.hasMatch(str)) {
            // Here str.replaceAll(".", "") chops the trailing dot from the input, and
            // finally the return statement is interpolated such that there is a dot
            // appended at the end of the number.
            return "${numberFormat01_and_02.format(int.parse(str.replaceAll(".", "")))}.";
          } else {
            if (myNumber03.hasMatch(str)) {
              lastChar = str[str.length - 1];
              print("########## lastChar=$lastChar");
              if (lastChar == "0") {
                String subStringBeforePeriod = "";
                String subStringAfterPeriod = "";
                String tmpStr = "";
    
                int indexOfPeriod = str.indexOf(".");
    
                subStringBeforePeriod = str.substring(0, indexOfPeriod);
                print("########## subStringBeforePeriod=$subStringBeforePeriod");
                subStringAfterPeriod = str.substring(indexOfPeriod + 1, str.length);
                print("########## subStringAfterPeriod=$subStringAfterPeriod");
    
                print("########## str with last char possibly = $str");
                // return numberFormat03.format(double.parse(str)) + lastChar;
                tmpStr =
                    numberFormat01_and_02.format(int.parse(subStringBeforePeriod));
                print("tmpStr=$tmpStr");
                // return numberFormat03.format(double.parse(str)) + lastChar;
                return "${tmpStr}.${subStringAfterPeriod}";
              }
              return numberFormat03.format(double.parse(str));
            } else {
              return "NaN";
            }
          }
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('intl Formatter With Formatted Text Field'),
            ),
            body: Padding(
              padding: const EdgeInsets.all(20.0),
              child: TextField(
                controller: _controller,
                keyboardType: TextInputType.number,
                onChanged: (value) {
                  print("########## value=$value");
                  value = value.replaceAll(",", "");
    
                  String myFormattedNumber = "";
                  myFormattedNumber = parseMyNumber(value);
                  if (myFormattedNumber != "NaN") {
                    RememberMyValue.value_previous_or_this_instance =
                        myFormattedNumber;
                    print(
                        "########## value_previous_or_this_instance = ${RememberMyValue.value_previous_or_this_instance}");
                  } else {
                    print("########## NaN");
                  }
    
                  _controller.value = TextEditingValue(
                    text: RememberMyValue.value_previous_or_this_instance,
                  );
                  print("########## _controller.text = ${_controller.text}");
                },
              ),
            ),
          ),
        );
      }
    }
    

    Snapshot:

    doubleInputFormatterImage