country_phone_picker 0.1.3
country_phone_picker: ^0.1.3 copied to clipboard
A Flutter package for selecting country phone codes.
Country Phone Picker #
A Flutter package for selecting countries with phone codes. It provides a bottom sheet picker with country flags, localized names, and dial codes — useful for phone number input forms.
Features #
- Country list with ISO codes, dial codes, and flags
- Bottom sheet picker UI
- Built-in search by country name, dial code, or ISO code
- Localized country names (69 languages)
- Phone length and valid starting digits per country
- Set the initial country with an ISO code (
initialCountryCode: 'US') - Hide the search field (
showSearch: false) - Limit the list to an ISO allowlist (
allowedCountryCodes: ['JO', 'SA']) - Simple
CountryPhonePickerwidget for quick integration - Performance: country data is parsed once, search runs only when the query changes, and keyboard animation does not rebuild the list
Installation #
Add this to your pubspec.yaml:
dependencies:
country_phone_picker: ^0.1.2
flutter_localizations:
sdk: flutter
Then run:
flutter pub get
Migrating from 0.0.2 #
Replace a full CountryModel with its ISO code. Use findCountryByIsoCode if you still need the model for hints or validation:
// Before
CountryPhonePicker(
initialCountry: myCountryModel,
...
)
// After
CountryPhonePicker(
initialCountryCode: 'US',
...
)
Basic Usage #
1. Setup localization #
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:country_phone_picker/country_phone_picker.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
supportedLocales: const [
Locale('en'),
Locale('ar'),
Locale('es'),
Locale('fr'),
],
localizationsDelegates: const [
CountryPickerLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: const HomePage(),
);
}
}
2. Use the picker #
bottomSheetTitle and onChanged are required.
CountryPhonePicker(
bottomSheetTitle: 'Choose Country',
initialCountryCode: 'US',
bottomSheetConfig: BottomSheetConfig(
closeIcon: Image.asset(
'assets/icons/close.png',
width: 20,
height: 20,
),
titleStyle: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
radioActiveColor: Colors.deepPurple,
searchConfig: const SearchConfig(hintText: 'Search country'),
),
onChanged: (CountryModel country) {
print(country.name);
print(country.dialCode);
print(country.isoCode);
},
)
Complete Example #
import 'package:flutter/material.dart';
import 'package:country_phone_picker/country_phone_picker.dart';
class PhoneInputExample extends StatefulWidget {
const PhoneInputExample({super.key});
@override
State<PhoneInputExample> createState() => _PhoneInputExampleState();
}
class _PhoneInputExampleState extends State<PhoneInputExample> {
static const String _initialCountryCode = 'US';
CountryModel selectedCountry =
findCountryByIsoCode(_initialCountryCode) ?? CountryModel.initialModel();
final TextEditingController phoneController = TextEditingController();
@override
void dispose() {
phoneController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Phone Number Input')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CountryPhonePicker(
bottomSheetTitle: 'Choose Country',
initialCountryCode: _initialCountryCode,
onChanged: (CountryModel country) {
setState(() => selectedCountry = country);
},
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: phoneController,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: selectedCountry.hintText ?? 'Phone number',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
],
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
final fullNumber =
'${selectedCountry.dialCode}${phoneController.text}';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Phone number: $fullNumber')),
);
},
child: const Text('Submit'),
),
],
),
),
);
}
}
See the full working app in the example directory:
cd example
flutter run
API Reference #
CountryPhonePicker #
| Property | Type | Required | Description |
|---|---|---|---|
onChanged |
ValueChanged<CountryModel> |
Yes | Called when a country is selected |
bottomSheetTitle |
String |
Yes | Title shown at the top of the bottom sheet |
bottomSheetConfig |
BottomSheetConfig |
No | Bottom sheet appearance; defaults to the standard package design |
initialCountryCode |
String? |
No | ISO 3166-1 alpha-2 code (e.g. "US") shown before the user picks one. Case-insensitive. null, empty, and unknown codes default to Jordan |
showSearch |
bool |
No | Whether the search field is shown. Defaults to true. The field is hidden when this is false or when SearchConfig.enabled is false |
allowedCountryCodes |
List<String>? |
No | ISO codes to keep in the list (e.g. ['JO', 'SA']). Case-insensitive. null or empty shows every country. Unknown codes are skipped. If none match, the full list is shown |
key |
Key? |
No | Widget key |
BottomSheetConfig #
An optional immutable configuration for the modal, header, search field, country rows, flags, radio controls, text, spacing, and separator. All properties have defaults, so you only need to provide the values you want to change.
The closeIcon accepts any widget. Assets are resolved from the host
application, while the package keeps ownership of the dismiss action.
The sheet takes the height its content needs, up to initialHeightFactor of the
available height, instead of taking the whole screen. A short list, such as the
two countries of allowedCountryCodes: ['JO', 'SA'], makes the sheet as tall as
those rows, and a long list stops at the factor. Tapping the search field raises
that ceiling to expandedHeightFactor first, and the keyboard is only requested
once the animation completes, so the field never slides under the keyboard.
Closing the keyboard shrinks the sheet back.
Set sizeToContent: false to always use the full height fraction, even when the
list is shorter than it.
When the sheet opens, the list scrolls to the country that is currently selected and centers it, so the previous choice is visible without scrolling.
| Property | Type | Default | Description |
|---|---|---|---|
initialHeightFactor |
double |
0.6 |
Maximum height fraction used when the sheet opens |
expandedHeightFactor |
double |
0.95 |
Maximum height fraction used after the search field is tapped |
sizeToContent |
bool |
true |
Shrink the sheet to the list height when it is shorter than the fraction |
expandOnSearchTap |
bool |
true |
Grow the sheet before showing the keyboard |
expandDuration |
Duration |
250ms |
Duration of the growth animation |
expandCurve |
Curve |
Curves.easeOutCubic |
Curve of the growth animation |
scrollToSelected |
bool |
true |
Scroll to the selected country when the sheet opens |
scrollToSelectedAlignment |
double |
0.5 |
Position of that country in the list: 0 top, 0.5 center, 1 bottom |
Set expandOnSearchTap: false to keep a fixed height and open the keyboard on
the first tap. Set scrollToSelected: false to always open at the top of the
list.
CountryPhonePickerBottomSheetConfigstill works as a deprecated alias ofBottomSheetConfig.
SearchConfig #
Controls the search field shown between the header and the country list. It is
enabled by default and filters on the localized country name, the fallback
package name, the ISO code, and the dial code (962, +962, and + 962 all
match Jordan).
| Property | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
true |
Whether the search field is shown |
autofocus |
bool |
false |
Focus the field when the sheet opens |
matcher |
CountrySearchMatcher? |
null |
Replaces the default filtering logic |
padding |
EdgeInsetsGeometry |
horizontal: 16 |
Padding around the field |
bottomSpacing |
double |
8 |
Space between the field and the list |
contentPadding |
EdgeInsetsGeometry? |
null |
Padding inside the field |
hintText |
String |
'Search' |
Placeholder text |
textStyle / hintStyle |
TextStyle? |
null |
Query and hint styles |
cursorColor |
Color? |
null |
Cursor color |
keyboardType |
TextInputType |
TextInputType.text |
Keyboard type |
textInputAction |
TextInputAction |
TextInputAction.search |
Keyboard action |
prefixIcon |
Widget? |
Icon(Icons.search) |
Leading widget, accepts host assets |
showClearButton |
bool |
true |
Show a clear button while typing |
clearIcon |
Widget |
Icon(Icons.clear) |
Clear button content |
filled / fillColor |
bool / Color? |
false / null |
Field background |
border / enabledBorder / focusedBorder |
InputBorder? |
null |
Field borders |
decoration |
InputDecoration? |
null |
Complete decoration override |
emptyResultText |
String |
'No countries found' |
Message when nothing matches |
emptyResultStyle |
TextStyle? |
null |
Style of the empty message |
emptyResultBuilder |
WidgetBuilder? |
null |
Replaces the empty message widget |
CountryPhonePicker(
bottomSheetTitle: 'Choose Country',
bottomSheetConfig: BottomSheetConfig(
initialHeightFactor: 0.6,
expandedHeightFactor: 0.95,
searchConfig: SearchConfig(
hintText: 'Search country or code',
autofocus: true,
filled: true,
fillColor: Colors.deepPurple.shade50,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
emptyResultText: 'No match',
),
),
onChanged: (CountryModel country) {},
)
To hide the field, or to filter with your own rules:
CountryPhonePicker(
showSearch: false,
...
);
const SearchConfig(enabled: false);
SearchConfig(
matcher: (CountryModel country, String query) =>
country.dialCode.contains(query),
);
To show only some countries:
CountryPhonePicker(
allowedCountryCodes: ['JO', 'SA'],
bottomSheetTitle: 'Choose Country',
onChanged: (CountryModel country) {},
);
Unknown codes in that list are skipped. If the list is null, empty, or contains no supported countries, every country is shown.
countriesForIsoCodes #
Resolves the same allowlist used by the picker. Returns the full country list when the input is null, empty, or has no supported codes.
final gulf = countriesForIsoCodes(['SA', 'AE', 'XX']);
CountryModel #
| Property | Type | Description |
|---|---|---|
code |
int |
Internal country id |
name |
String |
Fallback country name from package data |
isoCode |
String |
ISO 3166-1 alpha-2 code (e.g. "US") |
dialCode |
String |
Dial code (e.g. "+1") |
hintText |
String? |
Phone format hint (e.g. "77-XXXXXXX") |
lengthNumber |
int |
Expected national number length |
phoneStartsWith |
List<String> |
Valid starting digit prefixes |
Factories
CountryModel.initialModel()— default country (Jordan /JO/+962)CountryModel.fromJson(Map<String, dynamic> json)— create from a map
findCountryByIsoCode #
Looks up a country from the package list by ISO 3166-1 alpha-2 code (case-insensitive). Returns null if the code is unknown.
Use the same code you pass to CountryPhonePicker so the rest of your UI (hint, length, prefixes) matches the flag on first frame:
const iso = 'US';
final country = findCountryByIsoCode(iso) ?? CountryModel.initialModel();
CountryPhonePicker(
bottomSheetTitle: 'Choose Country',
initialCountryCode: iso,
onChanged: (next) { /* ... */ },
);
TextField(
decoration: InputDecoration(
hintText: country.hintText ?? 'Phone number',
),
);
To show a localized name in your own UI:
final name = CountryPickerLocalizations.of(context)
?.translate(country.isoCode) ??
country.name;
CountryPhonePickerBottomSheet #
Public bottom sheet widget if you want to present the list yourself.
| Property | Type | Required | Description |
|---|---|---|---|
selectedCountryCode |
CountryModel |
Yes | Currently selected country |
bottomSheetTitle |
String |
Yes | Sheet title |
config |
BottomSheetConfig |
No | Bottom sheet appearance |
showSearch |
bool |
No | Whether the search field is shown. Defaults to true |
allowedCountryCodes |
List<String>? |
No | ISO allowlist used for the sheet list |
Localization #
Register the delegate and locales you need:
localizationsDelegates: const [
CountryPickerLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
Force English names #
localizationsDelegates: [
CountryPickerLocalizations.getDelegate(enableLocalization: false),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
Supported language codes (69) #
af, am, ar, az, be, bg, bn, bs, ca, cs, da, de, el, en, es, et, fa, fi, fr, gl, ha, he, hi, hr, hu, hy, id, is, it, ja, ka, kk, km, ko, ku, ky, lt, lv, mk, ml, mn, ms, nb, nl, nn, no, pl, ps, pt, ro, ru, sd, sk, sl, so, sq, sr, sv, ta, tg, th, tr, tt, ug, uk, ur, uz, vi, zh
Phone number validation helpers #
CountryPhonePicker(
bottomSheetTitle: 'Choose Country',
onChanged: (CountryModel country) {
final isValidLength = phoneNumber.length == country.lengthNumber;
final hasValidStart = country.phoneStartsWith.any(
(prefix) => phoneNumber.startsWith(prefix),
);
if (isValidLength && hasValidStart) {
// Valid against package rules
}
},
)
Default country #
When initialCountryCode is omitted, the picker starts with Jordan:
| Field | Value |
|---|---|
| Name | Jordan |
| ISO | JO |
| Dial code | +962 |
| Hint | 7X-XXXXXXX |
| Length | 9 |
| Starts with | 77, 78, 79 |
Dependencies #
flutter(SDK)country_flags^4.1.2
Requirements #
- Flutter
>=1.17.0 - Dart
^3.7.2
Contributing #
Pull requests are welcome.
License #
See LICENSE.
Changelog #
See CHANGELOG.md.