phone_field_plus 1.0.0
phone_field_plus: ^1.0.0 copied to clipboard
Offline-first Flutter phone input with country picker and auto-detection.
phone_field_plus #
A highly customizable, offline-first Flutter phone number input widget with:
- π 200+ countries β fully hardcoded, zero network calls
- π Auto-detection β types
+216? Country switches to πΉπ³ Tunisia automatically - β Strict / loose validation per country (min/max length rules)
- π¨ Fully customizable β swap flags, list tiles, or the entire prefix
- π¦ Pub.dev ready β null-safe, clean architecture, tested
Installation #
dependencies:
phone_field_plus: ^1.0.0
Quick Start #
import 'package:phone_field_plus/phone_field_plus.dart';
PhoneField(
initialCountry: Countries.tunisia,
autoDetectCountry: true,
autoValidate: true,
onChanged: (PhoneValue value) {
print(value.e164); // +21612345678
print(value.country.isoCode); // TN
print(value.isValid); // true
},
)
PhoneField API #
| Parameter | Type | Default | Description |
|---|---|---|---|
initialCountry |
Country? |
Countries.unitedStates |
Country shown on first render |
countries |
List<Country>? |
Countries.all |
Countries available in picker |
autoDetectCountry |
bool |
true |
Auto-switch country from typed +code |
autoValidate |
bool |
false |
Validate on every keystroke |
validatorMode |
ValidatorMode |
strict |
strict or loose |
validator |
String? Function(PhoneValue)? |
β | Custom validator |
onChanged |
ValueChanged<PhoneValue>? |
β | Fires on every change |
onSubmitted |
ValueChanged<PhoneValue>? |
β | Fires on keyboard submit |
controller |
PhoneController? |
β | External controller |
decoration |
InputDecoration? |
β | Pass-through to TextField |
flagBuilder |
Widget Function(Country)? |
β | Custom flag widget |
countryPickerBuilder |
Widget Function(Country, VoidCallback)? |
β | Custom picker tile |
prefixBuilder |
Widget Function(Country, VoidCallback)? |
β | Replaces entire prefix |
PhoneValue #
class PhoneValue {
final Country country;
final String nationalNumber; // "12345678"
final String internationalNumber; // "+21612345678"
final String e164; // "+21612345678"
final bool isValid;
}
Countries #
// Named constants
Countries.tunisia // πΉπ³ +216
Countries.france // π«π· +33
Countries.unitedStates // πΊπΈ +1
// Groups
Countries.all // All 200+
Countries.africa // African countries
Countries.europe // European countries
Countries.asia // Asian countries
Countries.americas // North + South American countries
Countries.oceania // Oceanian countries
// O(1) lookup
Countries.byDialCode['216'] // β Countries.tunisia
Countries.byIsoCode['TN'] // β Countries.tunisia
Validation Modes #
// strict (default) β enforces country's exact min/max length
PhoneField(
validatorMode: ValidatorMode.strict, // e.g. France = exactly 9 digits
)
// loose β accepts partial input (β₯3 digits, β€maxLength)
PhoneField(
validatorMode: ValidatorMode.loose,
)
Auto-Detection #
The engine uses longest-prefix matching β critical for overlapping codes:
+2165... β +216 (Tunisia) β NOT +2 (some 2-digit code)
+1876... β +1876 (Jamaica) β NOT +1 (US/CA)
+331... β +33 (France) β NOT +3 (hypothetical)
PhoneController #
Use when you need programmatic control:
final controller = PhoneController(
initialCountry: Countries.france,
autoDetectCountry: true,
validatorMode: ValidatorMode.strict,
);
// Read current value
print(controller.phoneValue.e164);
// Switch country
controller.setCountry(Countries.germany);
// Use as a FormField validator
validator: (_) => controller.validate(),
Custom UI #
Custom flag #
PhoneField(
flagBuilder: (country) => Text(
country.flagEmoji,
style: TextStyle(fontSize: 24),
),
)
Custom picker tile #
PhoneField(
countryPickerBuilder: (country, onSelect) => ListTile(
leading: Text(country.flagEmoji),
title: Text(country.name),
subtitle: Text('+${country.dialCode}'),
onTap: onSelect,
),
)
Fully custom prefix #
PhoneField(
prefixBuilder: (country, onTap) => GestureDetector(
onTap: onTap,
child: Chip(label: Text('+${country.dialCode}')),
),
)
Form Integration #
Form(
key: _formKey,
child: PhoneField(
autoValidate: false,
validator: (value) {
if (!value.hasNumber) return 'Required';
if (!value.isValid) return 'Invalid number';
return null;
},
),
)
Country Model #
class Country {
final String name; // "Tunisia"
final String isoCode; // "TN"
final String dialCode; // "216" (no '+')
final String flagEmoji; // "πΉπ³"
final int minLength; // 8
final int maxLength; // 8
final String continent; // "Africa"
}
Architecture #
lib/
src/
models/
country.dart # Country data model
phone_value.dart # Immutable result object
data/
countries.dart # 200+ country database
widgets/
phone_field.dart # Main public widget
country_picker.dart # Bottom sheet picker
controllers/
phone_controller.dart # State, parsing, validation
utils/
phone_parser.dart # Pure parsing functions
country_resolver.dart # Longest-prefix matching engine
License #
MIT