Phone number input masks on Flutter

Hi all. We need to talk a little about such a standard and ordinary part of any application as entering the user’s phone number. It’s only about Russian phones. It would seem, what could be easier? We put + or even +7 at the beginning of the line and the user indicates the phone number starting from 9. For convenience, you can put on a mask and it would seem that everything is fine. But no, sometimes users dlob…. they look in the wrong direction and write from 8 or 7 and that’s it. Without authorization, the user cannot use the service and give us money for doshik for smoothies. Let’s look at some examples from other services:

How it works in wildberries
The logic is simple: put + and then the user himself will understand what to enter.

The logic is simple: put + and then the user himself will understand what to enter.

How it works in Tinkoff

Task

We are making a package for users from Russia and a couple of random foreigners. Our requirements:

  • The field is empty by default. The user decides what and how to enter

  • when entering 7,8,9, the mask of Russian numbers should work (X (XXX) XXX-XX-XX),

  • You can also enter a foreign number into the mask. For example +1

Time to pound on the keyboard

For work, we will use the successor of TextInputFormatter. It can be added to any text field in inputFormatters. And then pull out all the information we need.

class RuPhoneInputFormatter extends TextInputFormatter{
  @override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    
  }
}

For work we need

  1. variable for formatted value

  2. Is this a Russian flag number

  3. number formatting method

  4. clean number getter (number without formatting. For Russian it starts from 9, for foreign it starts from the first digit in the text field)

Add the necessary variables and default values ​​​​(suddenly we already know the number)

class RuPhoneInputFormatter extends TextInputFormatter{
  //форматированный телефон
  String _formattedPhone = "";
  //Российский ли номер
  bool _isRu=false;

  //Добавляем возможность указать номер по дефолту
  RuPhoneInputFormatter({
    String? initialText,
  }) {
    if (initialText != null) {
      formatEditUpdate(
          TextEditingValue.empty, TextEditingValue(text: initialText));
    }
  }

    ///Иетод возвращает форматированнный телефон
  String getMaskedPhone() {
    return _formattedPhone;
  }
  ///возвращает чистый телефон. для России начинается с 9
  String getClearPhone() {
    if(_formattedPhone.isEmpty){
      return '';
    }
    if(!_isRu){
      return _formattedPhone.replaceAll(RegExp(r'\D'), '');
    }
    return _formattedPhone.replaceAll(RegExp(r'\D'), '').substring(
        1,
        (_formattedPhone.replaceAll(RegExp(r'\D'), '').length >= 11)
            ? 11
            : _formattedPhone.replaceAll(RegExp(r'\D'), '').length);
  }
  ///Проверяет заполнил ли пользователь телефон. Актуально только для Российских телефонов
  bool isDone(){
    if(!_isRu){
      return true;
    }
    return (_formattedPhone.replaceAll(RegExp(r'\D'), '').length>10);
  }
  ///возвращает флаг Российски ли номер
  get isRussian=>_isRu;
}

Writing a method for formatting

method should check leading digits and only format if it’s 7,8,9

String _formattingPhone(String text){
  //регулярка протиа букв. в телефоне только цифры
    text=text.replaceAll(RegExp(r'\D'), '');
    if(text.isNotEmpty){
      String phone="";
      //проверяем российски ли номер
      if(['7','8','9'].contains(text[0])){
        _isRu=true;
        //если пользователь начал с 9, то добавим 7
        if(text[0]=='9'){
          text="7$text";
        }
        //Проверяем нужен ли +
        String firstSymbols=(text[0]=='8') ? '8':'+7';
        //само форматирование
        phone="$firstSymbols ";
        if(text.length>1){
          phone+='(${text.substring(1,(text.length<4)?text.length:4)}';
        }if(text.length>=5){
          phone+=') ${text.substring(4,(text.length<7)?text.length:7)}';
        }
        if(text.length>=8){
          phone+='-${text.substring(7,(text.length<9)?text.length:9)}';
        }
        if(text.length>=10){
          phone+='-${text.substring(9,(text.length<11)?text.length:11)}';
        }
        return phone;
      }else{
        _isRu=false;
        return '+$text';
      }
    }
    return '';
  }

Putting it all together

Let’s add a code for the convenience of editing the phone

@override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    String text=newValue.text.replaceAll(RegExp(r'\D'), '');
    int selectionStart=oldValue.selection.end;

    //проверяем стерает ли пользователь все символы?
    if(oldValue.text=='${newValue.text} '){
      _formattedPhone="";
      return TextEditingValue(
          text: _formattedPhone,
          selection: TextSelection(
              baseOffset: _formattedPhone.length,
              extentOffset: _formattedPhone.length,
              affinity: newValue.selection.affinity,
              isDirectional: newValue.selection.isDirectional
          )
      );
    }

    //проверяем редактирует ли пользователь телефон где то по середине?
    if(selectionStart!=_formattedPhone.length){
      _formattedPhone= _formattingPhone(text);
      //если да, то не перекидываем курсов в самый конец

      return TextEditingValue(
          text: _formattedPhone,
          selection: TextSelection(
              baseOffset: newValue.selection.baseOffset,
              extentOffset: newValue.selection.extentOffset,
              affinity: newValue.selection.affinity,
              isDirectional: newValue.selection.isDirectional
          )
      );
    }

    _formattedPhone= _formattingPhone(text);

    //если пользователь просто вводит телефон, 
    //то переносим курсор в конец форматированной строки
    return TextEditingValue(
        text: _formattedPhone,
        selection: TextSelection(
            baseOffset: _formattedPhone.length,
            extentOffset: _formattedPhone.length,
            affinity: newValue.selection.affinity,
            isDirectional: newValue.selection.isDirectional
        )
    );
  }

Results

Widget example.  the user is completely free to enter the phone.  Support for foreign phones has also been preserved.

Widget example. the user is completely free to enter the phone. Support for foreign phones has also been preserved.

What’s next?

plans to add masks for all phones of the CIS countries.

Plastic bag

For convenience, all sources here

There is also a package on pub.dev here

Support the author here

I drew inspiration from a video on Youtube

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *