country_phone_field 0.1.1 copy "country_phone_field: ^0.1.1" to clipboard
country_phone_field: ^0.1.1 copied to clipboard

[pending analysis]

A highly customizable international phone number input field: searchable country picker, per-country validation, flexible styling, and E.164 output.

country_phone_field #

A highly customizable international phone number input field for Flutter.

Pick a country from a searchable picker, type a number that is automatically filtered and length-limited for that country, validate it, and get back a clean PhoneNumber value that keeps the country separated from the rest of the number.

  • 🌍 Bundled catalogue of ~190 countries (flag, dial code, ISO code, length rules)
  • 🎨 Style it any way you like — outlined, underlined, filled, borderless, or a fully custom InputDecoration
  • 🔎 Searchable country picker (bottom sheet or dialog), favorites, custom rows, or your own picker entirely
  • ✅ Built-in required + per-country length validation, or bring your own validator
  • 📦 Emits E.164 (+263771234567) and the parts (country + nationalNumber) separately
  • 🔒 Lockable to a single country (e.g. mobile-money flows)
  • 🌗 Theme-aware (light/dark) out of the box, fully overridable
  • 🌐 Every user-facing string is overridable for localization

Install #

dependencies:
  country_phone_field: ^0.1.0
import 'package:country_phone_field/country_phone_field.dart';

Quick start #

PhoneNumberField(
  initialCountry: Countries.zimbabwe,
  onChanged: (phone) {
    print(phone.country.isoCode);   // ZW
    print(phone.nationalNumber);    // 771234567
    print(phone.completeNumber);    // +263771234567  (E.164)
    print(phone.isValid);           // true
  },
);

The PhoneNumber output #

Every callback (onChanged, onSubmitted, onSaved, validator) gives you a PhoneNumber, which separates the country from the number:

Member Description
country The selected Country (dial code, flag, ISO code, length rules)
nationalNumber The number the user typed — digits only, no dial code
completeNumber / e164 Joined E.164 form, e.g. +263771234567
isValid true when the length matches the country's rules
isEmpty / isNotEmpty Whether any digits were entered

Hydrate a field from a stored E.164 string:

final parsed = PhoneNumber.tryParse('+263771234567');
// parsed.country == Countries.zimbabwe, parsed.nationalNumber == '771234567'

Validation & verification #

PhoneNumberField validates length against the selected country's minLength/maxLength and integrates with Form:

final formKey = GlobalKey<FormState>();

Form(
  key: formKey,
  child: PhoneNumberField(
    initialCountry: Countries.kenya,
    required: true,           // empty => error (default)
    validateLength: true,     // enforce per-country digit count (default)
    onSaved: (phone) => _store(phone.completeNumber),
    validator: (phone) {      // optional: runs first, wins if it returns a message
      if (phone.nationalNumber.startsWith('0')) return 'Drop the leading 0';
      return null;
    },
  ),
);

if (formKey.currentState!.validate()) {
  formKey.currentState!.save();
}

Note on verification. Length validation is a fast structural check, not proof that a line exists. For real verification, pair this field with an SMS OTP (or similar) step using phone.completeNumber.

Styling — pick your level #

1. Convenience knobs (no InputDecoration needed) #

PhoneNumberField(
  borderType: PhoneFieldBorderType.outline,   // outline | underline | none
  borderRadius: 12,
  filled: true,
  fillColor: Colors.white,
  borderColor: Colors.grey,
  focusedBorderColor: Colors.indigo,
  errorColor: Colors.red,
  contentPadding: EdgeInsets.all(16),
  textStyle: TextStyle(fontSize: 16),
  cursorColor: Colors.indigo,
);

Borderless / filled is just borderType: PhoneFieldBorderType.none with filled: true. Underlined is PhoneFieldBorderType.underline.

2. Fully custom InputDecoration #

When you pass decoration, it wins — the country selector is injected as the prefixIcon (unless you set one yourself):

PhoneNumberField(
  decoration: InputDecoration(
    labelText: 'WhatsApp',
    filled: true,
    enabledBorder: OutlineInputBorder(
      borderRadius: BorderRadius.circular(8),
      borderSide: BorderSide(color: Colors.teal),
    ),
  ),
);

3. The selector (flag + dial code prefix) #

PhoneNumberField(
  selectorStyle: CountrySelectorStyle(
    showFlag: true,
    showDialCode: true,
    showIsoCode: false,
    showDropdownIcon: true,
    showDivider: true,
    flagSize: 22,
    dialCodeStyle: TextStyle(fontWeight: FontWeight.bold),
    dropdownIcon: Icon(Icons.expand_more),
    // ...or replace it entirely:
    builder: (context, country, enabled) => Text(country.flag),
  ),
);

The country picker #

PhoneNumberField(
  pickerConfig: CountryPickerConfig(
    type: CountryPickerType.bottomSheet,   // or .dialog
    searchable: true,
    favorites: ['ZW', 'ZA', 'KE', 'NG'],   // pinned to the top
    initialChildSize: 0.7,
    backgroundColor: Colors.white,
    borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
    itemBuilder: (context, country, isSelected, onTap) => ListTile(
      onTap: onTap,
      title: Text('${country.flag}  ${country.name}'),
    ),
  ),
);

Or replace the picker completely:

PhoneNumberField(
  pickerBuilder: (context, countries, selected) async {
    return await myCustomCountryChooser(context, countries);
  },
);

Lock to a single country #

PhoneNumberField(
  lockedCountry: Countries.zimbabwe, // picker disabled, dropdown arrow hidden
  labels: PhoneFieldLabels(labelText: 'EcoCash number'),
);

Restrict / extend the catalogue #

// Only a subset:
PhoneNumberField(countries: [Countries.kenya, Countries.zimbabwe, Countries.southAfrica]);

// Look things up:
Countries.fromIsoCode('ZW');        // Country?
Countries.fromDialCode('+263...');  // longest-prefix match
Countries.parse('+263771234567');   // (country, nationalNumber)

// Customize an entry or add your own:
final c = Countries.zimbabwe.copyWith(name: 'Zim');
const custom = Country(name: 'Narnia', isoCode: 'NA', dialCode: '+999', minLength: 6, maxLength: 8);

Localization #

Every string is overridable:

PhoneNumberField(
  labels: PhoneFieldLabels(
    labelText: 'Numéro de téléphone',
    searchHint: 'Rechercher…',
    pickerTitle: 'Choisir un pays',
    requiredError: 'Numéro requis',
    lengthError: (c) => 'Numéro ${c.name} invalide',
  ),
);

Responsiveness #

The field expands to its parent's width and the selector sizes to its content, so it adapts to phones, tablets and web. Constrain it with normal layout widgets (SizedBox, Expanded, ConstrainedBox, etc.). The picker uses a draggable sheet on small screens and works as a centered dialog (type: CountryPickerType.dialog) on larger ones.

Example #

A full gallery (six styles, live output, theme toggle, form validation) lives in example/:

cd example && flutter run

API surface #

PhoneNumberField, PhoneNumber, Country, Countries, CountrySelectorStyle, CountryPickerConfig, CountryPickerType, PhoneFieldLabels, PhoneFieldBorderType, showCountryPicker.

License #

See LICENSE.

0
likes
0
points
--
downloads

Publisher

unverified uploader

A highly customizable international phone number input field: searchable country picker, per-country validation, flexible styling, and E.164 output.

Repository (GitHub)
View/report issues

License

(pending) (license)

Dependencies

flutter

More

Packages that depend on country_phone_field