DEV Community

Cover image for Number input on Flutter TextFields the RIGHT way!
Oranekwu Gabriel Ekene
Oranekwu Gabriel Ekene

Posted on

Number input on Flutter TextFields the RIGHT way!

Eureka!

That was my expression a few days ago, when I found the perfect algorithm to XYZ. You should have seen the excitement on my face, I couldn't wait for the weekend to finally share the experience 😁.

Since Flutter is used to develop Iphone and Android applications (two different platforms -> with two different keypads), I'll explain my thoughts clearly with images from an Emulator (Android) and a Simulator (iOS).

Let's dive in ...

NUMBER INPUT ON FLUTTER TEXTFIELD

In Flutter, when we want to collect number inputs from users, we set the keyboardType property of a TextField widget to TextInputType.number

using number display

Once you do this, you're simply asking the device's keyboard to display it's number display.

side by side

From the two images above, we are able to collect what we need, right? and, it's largely sufficient, right?

Most Flutter developers will stop at doing this. However, this article aims to expose the RIGHT WAY!

While the above code is enough to collect a user's age, it's not entirely good user experience, because, on a Platform like Android, user's will be able to type hyphens, commas, blank spaces and period (This particular concern will not be an issue on iOS, however, we are coming to that).

Android bad

What we can do is; Use the inputFormatters property of a TextField object to filter for just numbers. This ensures we constrain our inputs to DIGITS only.

digits only

DECIMAL INPUT ON FLUTTER TEXTFIELD

While the above solutions does exactly what we want, it is important I point out that, this is recommended if you're collecting just WHOLE NUMBERS from the user, a typical example being an age. If we are collecting decimals, say, a person's weight, we need to go through another path. Because, our first constraint does not allow period(decimals).
allow

Android decimal

While this seems like a very nice approach, we are faced with a challenge, users are able to type a period more than once 😱, and we don't want that, AT ALL!

We need to find a way to stop users from entering a period more than once, luckily, the inputFormatters takes a List of TextInputFormatter, we can create our own custom TextInputFormatter.

class SinglePeriodEnforcer extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
    final newText = newValue.text;
    // Allow only one period
    if ('.'.allMatches(newText).length <= 1) {
      return newValue;
    }
    return oldValue;
  }
}
Enter fullscreen mode Exit fullscreen mode

Single period enforcer

Awesome, there's yet another issue, and that is with Iphones, our current keyboardType doesn't show a period on Iphones, remember the purpose of a cross-platform framework like Flutter is to write once and deploy everywhere.

Iphone number only

Our solution would be to change the keyboard type to TextInputType.numberWithOptions and set the decimal parameter to true. This will ensure the period key appears at the bottom left.

Iphone decimal

You can explore the signed property of the TextInputType.numberWithOptions.

FORMAT LIKE A PRO 😎

While the two solutions above are largely sufficient for the kind of data we intend to collect from the user, I want to take the TextInputFormatter up a notch; that is where the "Aha" moment came, the "XYZ" solution.

The Challenge

When we want to collect information, such as an amount from a user, solution 2 above will be very nice, since the user is allowed to type decimals, however, I want to seperate the digits entered with "commas" automatically, without the user needing to do anything. This approach, I believe is very intuitive, it helps users visualise whatever amount they are entering, since it's comma seperated.

The Solution

For this solution, we need the intl package. Just include it in your pubspec.yaml file.

flutter intl package

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

class ThousandsSeparatorInputFormatter extends TextInputFormatter {
  // Setup a formatter that supports both commas for thousands and decimals
  final formatter = NumberFormat("#,##0.###");

  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue, TextEditingValue newValue) {
    if (newValue.text.isEmpty) {
      return newValue;
    }
    // Remove commas to check the new input and for parsing
    final newText = newValue.text.replaceAll(',', '');
    // Try parsing the input as a double
    final num? newTextAsNum = num.tryParse(newText);

    if (newTextAsNum == null) {
      return oldValue; // Return old value if new value is not a number
    }

    // Split the input into whole number and decimal parts
    final parts = newText.split('.');
    if (parts.length > 1) {
      // If there's a decimal part, format accordingly
      final integerPart = int.tryParse(parts[0]) ?? 0;
      final decimalPart = parts[1];
      // Handle edge case where decimal part is present but empty (user just typed the dot)
      final formattedText = '${formatter.format(integerPart)}.$decimalPart';
      return TextEditingValue(
        text: formattedText,
        selection: updateCursorPosition(formattedText),
      );
    } else {
      // No decimal part, format the whole number
      final newFormattedText = formatter.format(newTextAsNum);
      return TextEditingValue(
        text: newFormattedText,
        selection: updateCursorPosition(newFormattedText),
      );
    }
  }

  TextSelection updateCursorPosition(String text) {
    return TextSelection.collapsed(offset: text.length);
  }
}
Enter fullscreen mode Exit fullscreen mode

Then, we use it, like so:

Thousand seperator

We end up with something like this, a nicely formatted text input filter for collecting information, such as an amount

both done

However 😩, we have one more tiny thing to fix, the value collected from the field has a "comma", we need to do away with that (We cannot be sending values with commas to the backend), you can create a function to strip the comma off, or you create an extension. I prefer the later though (Just a personal preference).

So, let's go ahead and create an extension on String

Comma formatter

You just need to use it whereever you are collecting the value from the Field.

usage

Here, I used the extension on the onChanged parameter of a TextField.

It's also important to note that; all the solutions we did works just as well on a TextFormField.

If you have any questions related to this article, you can leave them in the comment section and I'll attend to them as soon as I can 😊.

HAPPY CODING! ✌️

Feel free to connect with me:

Top comments (5)

Collapse
 
fiki2002 profile image
Adepitan Oluwatosin

Nice read!!!
Thank you Gabby

Collapse
 
gabbygreat profile image
Oranekwu Gabriel Ekene

Thanks man

Collapse
 
jumzeey profile image
Jumzeey

Nice write up. Very packed

Collapse
 
gabbygreat profile image
Oranekwu Gabriel Ekene

Thanks man

Collapse
 
amakor_victor profile image
Amakor Victor

Thankyou Gabby for this exposition, this will really come in handy when working on complex input fields