flutter_country_phone

Country catalog, localized country picker, SVG flags, and international phone form fields for Flutter apps.

flutter_country_phone gives you a reusable foundation for forms that need a country selector, a phone input with country prefix handling, or a simple way to render country names and flags. It ships with its own static country catalog and flag assets, so your app does not need to duplicate those resources.

Features

  • Static country catalog with ISO 3166-1 alpha-2 codes, phone prefixes, and localized country names.
  • Package-owned SVG flags through CountryFlag.
  • CountryBloc to load and reuse the country catalog across fields.
  • CountryFormField for selecting a country inside a Form.
  • PhoneFormField for collecting an international phone number.
  • CountryField and PhoneField lower-level widgets when you do not need a FormField wrapper.
  • Search by localized country name, ISO code, or phone prefix.
  • onlyCountries filtering for restricted country lists.
  • CountryPhoneFieldStyle plus item/button builders for UI customization.
  • Locale fallback support for values such as es, es_MX, es-MX, pt_BR, and pt-BR.

Installation

Add the package to your pubspec.yaml:

dependencies:
  flutter_country_phone: ^0.1.1

The package declares its own flag assets. No extra asset registration is needed in your app.

Basic setup

Provide a CountryBloc once near the part of the app that uses country or phone fields:

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_country_phone/flutter_country_phone.dart';

BlocProvider(
  create: (_) => CountryBloc()..fetchCountries(),
  child: const MyApp(),
)

You can also register CountryBloc in your app-level MultiBlocProvider if the country catalog is used in multiple features.

Country picker

Use CountryFormField when the selected country is part of a Form:

CountryFormField(
  value: 'US',
  decoration: const InputDecoration(
    labelText: 'Country',
    hintText: 'Select your country',
  ),
  validator: (country) {
    if (country == null) return 'Select a country';
    return null;
  },
  onChanged: (country) {
    debugPrint(country?.iso2); // US
  },
)

Restrict the list when a flow supports only a few countries:

CountryFormField(
  onlyCountries: const ['US', 'BR', 'ES'],
  showPhoneCode: true,
  onChanged: (country) {
    debugPrint('${country?.iso2} ${country?.phoneCode}');
  },
)

If you need a plain widget instead of a form field, use CountryField:

CountryField(
  decoration: const InputDecoration(labelText: 'Country'),
  locale: 'pt-BR',
  onChanged: (country) {
    debugPrint(country.iso2);
  },
)

International phone field

PhoneFormField lets the user select a country first and then enter the national phone number. The onChanged value is the international number string generated by phone_numbers_parser.

PhoneFormField(
  decoration: const InputDecoration(
    labelText: 'Phone number',
    hintText: 'Enter your phone number',
  ),
  textInputAction: TextInputAction.next,
  autofillHints: const [AutofillHints.telephoneNumber],
  validator: (value) {
    if (value == null || value.isEmpty) return 'Enter a phone number';
    return null;
  },
  onChanged: (value) {
    debugPrint(value); // Example: +15551234567
  },
)

You can pass an existing international number as the initial value:

PhoneFormField(
  initialValue: '+5511999999999',
  decoration: const InputDecoration(labelText: 'Phone'),
)

When the initial value is parseable and its country is allowed, the field opens directly in phone-entry mode with the matching country selected.

Rendering countries and flags

Use CountryFlag when you only need a flag:

const CountryFlag(
  isoCode: 'BR',
  size: 24,
  borderRadius: BorderRadius.all(Radius.circular(4)),
)

Use CountryWidget when you need the localized country name with the flag:

const CountryWidget(
  iso: 'ES',
  flagSize: 18,
)

CountryWidget also reads from CountryBloc, so make sure the bloc is available in the widget tree.

Localization

Widgets use Localizations.localeOf(context) by default. You can override the locale when a field needs to render in a specific language:

CountryFormField(
  locale: 'es-MX',
  decoration: const InputDecoration(labelText: 'PaĆ­s'),
)

Locale values can be language-only (es) or region-specific (es_MX, es-MX, pt_BR, pt-BR). The lookup tries the full locale, then the language code, then English, then the first non-empty available value.

You can also customize labels with a builder:

CountryFormField(
  countryLabelBuilder: (context, country) {
    return country.localizedName('pt-BR');
  },
)

For app-specific empty states, pass emptyBuilder.

Styling

Most visual customization is intentionally grouped into CountryPhoneFieldStyle:

const countryPhoneStyle = CountryPhoneFieldStyle(
  overlayMaxHeight: 260,
  overlayBorderRadius: BorderRadius.all(Radius.circular(18)),
  overlayElevation: 10,
  overlayPadding: EdgeInsets.all(8),
  itemPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
  itemBorderRadius: BorderRadius.all(Radius.circular(14)),
  flagSize: 24,
  selectedFlagSize: 20,
  flagFit: BoxFit.cover,
);

CountryFormField(
  style: countryPhoneStyle,
)

For deeper customization, replace the country item or selected-country button:

PhoneFormField(
  countryItemBuilder: (context, country, selected) {
    return ListTile(
      leading: CountryFlag(isoCode: country.iso2, size: 22),
      title: Text(country.localizedName('es')),
      trailing: Text(country.phoneCode),
      selected: selected,
    );
  },
  selectedCountryButtonBuilder: (context, country, onPressed) {
    return TextButton.icon(
      onPressed: onPressed,
      icon: CountryFlag(isoCode: country.iso2),
      label: Text(country.phoneCode),
    );
  },
)

Working with the catalog

You can use the catalog directly through the datasource/use case stack:

final bloc = CountryBloc();

await bloc.fetchCountries();

final countries = bloc.countries;
final unitedStates = countries.firstWhere((country) => country.iso2 == 'US');

CountryBloc keeps the loaded countries in memory. Calling fetchCountries() again will reuse the current list unless force: true is passed internally by refreshCountries().

API notes

  • CountryEntity is immutable, extends Equatable, and includes copyWith.
  • CountryModel extends CountryEntity and provides fromJson, fromEntity, and toJson.
  • CountryModel.fromJson accepts phoneCode or phone_code and normalizes ISO codes to uppercase.
  • Phone formatting and parsing are delegated to phone_numbers_parser.
  • The package is UI-framework friendly: use the widgets directly or keep the domain/data classes for app-specific wrappers.

Development

Run the local checks before publishing:

flutter analyze
flutter test
flutter pub publish --dry-run

License

MIT License. See LICENSE.