πŸ“± phone_input_plus

A customizable Flutter phone input field with automatic country detection, smart formatting, and validation. Perfect for African and international phone numbers.

pub package pub points popularity likes License: MIT

✨ Features

  • Auto Country Detection - Detects user's country via IP or device locale
  • Intelligent Paste Detection - Auto-detects country from pasted international numbers
  • Smart Formatting - Real-time number formatting as you type
  • Dynamic Length Limitation - Automatically limits input based on country
  • Adaptive Keyboard - Automatically chooses the best keyboard type
  • 5 Predefined Styles - Beautiful ready-to-use visual styles
  • Copy Button - One-tap copy of formatted phone numbers
  • Visual Feedback - Animated validation and smooth transitions
  • Built-in Validation - Country-specific phone number validation
  • Highly Customizable - Extensive styling and configuration options
  • 52 African Countries - Comprehensive support for African phone numbers
  • International Support - Works worldwide with any country
  • Multiple Selector Types - BottomSheet, Dialog, or Full Page
  • Search Functionality - Quick country search with multi-language support
  • Smart Caching - Caches detected country for better performance
  • Controller Support - Full programmatic control like TextField

πŸ“Έ Screenshots

Basic Basic Example BottomSheet Country Controller Example Dialog Country Validation Example

πŸš€ Getting Started

Installation

Add this to your pubspec.yaml:

dependencies:
  phone_input_plus: ^0.0.1

Then run:

flutter pub get

Import

import 'package:phone_input_plus/phone_input_plus.dart';

πŸ’‘ Basic Usage

Simple Phone Input

The most basic usage with auto country detection:

PhoneInputField(
  decoration: const InputDecoration(
    labelText: 'Phone Number',
    border: OutlineInputBorder(),
  ),
  onChanged: (PhoneNumber phone) {
    print('International: ${phone.international}');
    print('Valid: ${phone.isValid}');
  },
)

With Initial Country

Set a specific initial country:

PhoneInputField(
  initialCountry: CountryData.benin,
  autoDetect: false, // Disable auto-detection
  decoration: const InputDecoration(
    labelText: 'Phone Number',
    border: OutlineInputBorder(),
  ),
)

Limited Country List

Restrict to specific countries (e.g., West Africa only):

PhoneInputField(
  countries: const [
    CountryData.benin,
    CountryData.gabon,
    CountryData.senegal,
    CountryData.coteDivoire,
    CountryData.cameroon,
  ],
  decoration: const InputDecoration(
    labelText: 'Phone Number',
    border: OutlineInputBorder(),
  ),
)

🎨 Visual Styles

Predefined Styles

Choose from 5 beautiful predefined styles:

// Modern style with animations and feedback
PhoneInputField(
  style: PhoneInputStyle.modern,
  decoration: const InputDecoration(
    labelText: 'Phone Number',
    border: OutlineInputBorder(),
  ),
)

// Minimal style - ultra clean
PhoneInputField(
  style: PhoneInputStyle.minimal,
  decoration: const InputDecoration(
    labelText: 'Phone',
  ),
)

// Rounded style - iOS-like
PhoneInputField(
  style: PhoneInputStyle.rounded,
  decoration: const InputDecoration(
    labelText: 'Phone',
    border: InputBorder.none,
  ),
)

// Standard and Outlined also available

Available Styles

Style Description Features
standard Clean default style Simple and professional
modern Animated with feedback βœ“ Validation icon, shake animation, dynamic colors
minimal Ultra clean Underline only, compact
rounded iOS-like Rounded corners, background fill
outlined Material emphasized Bold borders

Custom Theme

Create your own custom style:

PhoneInputField(
  theme: PhoneInputTheme(
    borderRadius: BorderRadius.circular(16),
    showValidationIcon: true,
    enableShakeAnimation: true,
    validBorderColor: Colors.purple,
    invalidBorderColor: Colors.orange,
    focusedBorderColor: Colors.deepPurple,
    borderWidth: 2.5,
  ),
)

Mix Styles

Start with a preset and customize:

PhoneInputField(
  theme: PhoneInputTheme.modern().copyWith(
    validBorderColor: Colors.blue,
    borderRadius: BorderRadius.circular(20),
  ),
)

πŸ“‹ Copy Button

Enable a copy button to let users copy the formatted phone number with one tap:

PhoneInputField(
  showCopyButton: true,  // Enable copy button
  decoration: const InputDecoration(
    labelText: 'Phone Number',
    border: OutlineInputBorder(),
  ),
)

Customize Copy Button

PhoneInputField(
  showCopyButton: true,
  copyButtonIcon: Icons.content_copy,  // Custom icon
  copiedMessage: 'Number copied!',  // Custom message
)

Combine with Validation

The copy button works seamlessly with validation icons:

PhoneInputField(
  style: PhoneInputStyle.modern,  // Has validation icon
  showCopyButton: true,  // Add copy button
  // Both icons appear side-by-side
)

🎯 Advanced Usage

Using PhoneController

For programmatic control, use PhoneController:

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late PhoneController _controller;

  @override
  void initState() {
    super.initState();
    _controller = PhoneController(
      initialCountry: CountryData.benin,
    );
    
    // Listen to changes
    _controller.addListener(() {
      print('Phone changed: ${_controller.international}');
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        PhoneInputField(
          controller: _controller,
          decoration: const InputDecoration(
            labelText: 'Phone Number',
            border: OutlineInputBorder(),
          ),
        ),
        
        // Programmatic actions
        ElevatedButton(
          onPressed: () => _controller.changeCountry(CountryData.france),
          child: const Text('Switch to France'),
        ),
        ElevatedButton(
          onPressed: () => _controller.clear(),
          child: const Text('Clear'),
        ),
      ],
    );
  }
}

Form Validation

Integrate with Flutter forms:

Form(
  key: _formKey,
  child: PhoneInputField(
    decoration: const InputDecoration(
      labelText: 'Phone Number *',
      border: OutlineInputBorder(),
    ),
    validator: (phone) {
      if (phone == null || phone.isEmpty) {
        return 'Phone number is required';
      }
      if (!phone.isValid) {
        return 'Invalid phone number for ${phone.country.nameEn}';
      }
      return null;
    },
    onSubmitted: (phone) {
      if (_formKey.currentState!.validate()) {
        // Submit form
      }
    },
  ),
)

⌨️ Keyboard Type & Paste Detection

Intelligent Paste Detection

By default, PhoneInputField supports intelligent paste detection. When a user pastes an international number like +2290166640219, the widget automatically:

  1. Detects the country (Benin in this case)
  2. Changes the country selector
  3. Extracts the national number
  4. Formats it correctly
PhoneInputField(
  enablePasteDetection: true,  // Default
  decoration: const InputDecoration(
    labelText: 'Phone Number',
    hintText: 'Paste +2290166640219',
    border: OutlineInputBorder(),
  ),
)

Keyboard Behavior

The keyboard type is automatically chosen based on paste detection:

Paste Detection Keyboard Type Behavior
Enabled (default) Text keyboard Allows pasting numbers with +
Disabled Numeric keyboard Better UX for manual entry

Disable paste detection for numeric keyboard:

PhoneInputField(
  enablePasteDetection: false,  // Numeric keyboard
  decoration: const InputDecoration(
    labelText: 'Phone Number',
    border: OutlineInputBorder(),
  ),
)

Manual Override

You can manually specify the keyboard type:

PhoneInputField(
  keyboardType: TextInputType.phone,  // Force numeric keyboard
  enablePasteDetection: false,  // Disable paste (won't work with numeric)
)

Or try to keep both features with:

PhoneInputField(
  keyboardType: const TextInputType.numberWithOptions(signed: true),
  enablePasteDetection: true,  // May work on some devices
)

Note: The ability to paste numbers with + on numeric keyboards varies by device and OS.

🌍 Country Detection

The package automatically detects the user's country using multiple strategies:

  1. IP-based detection (primary) - Uses free IP geolocation APIs
  2. Device locale (fallback) - Uses the device's locale settings
  3. Smart caching - Caches the detected country for 24 hours

Disable Auto-Detection

PhoneInputField(
  autoDetect: false,
  initialCountry: CountryData.benin,
)

Custom Cache Duration

PhoneInputField(
  detectionCacheDuration: 48, // Cache for 48 hours
)

βœ… Validation

Built-in Validation

Each country has specific validation rules:

final phone = PhoneNumber(
  country: CountryData.benin,
  nationalNumber: '0166640219',
);

print(phone.isValid); // true - valid Benin number
print(phone.formatted); // "01 66 64 02 19"
print(phone.international); // "+2290166640219"

Custom Validator

Add your own validation logic:

PhoneInputField(
  validator: (phone) {
    if (phone == null || phone.isEmpty) {
      return 'Required';
    }
    
    // Custom rule: Only mobile numbers
    if (phone.country.code == 'FR' && 
        !phone.nationalNumber.startsWith('6') &&
        !phone.nationalNumber.startsWith('7')) {
      return 'Only mobile numbers allowed';
    }
    
    if (!phone.isValid) {
      return 'Invalid number';
    }
    
    return null;
  },
)

🎨 Customization

Country Button Style

Customize the country selector button:

PhoneInputField(
  countryButtonStyle: const CountryButtonStyle(
    showDialCode: false, // Hide dial code
    padding: EdgeInsets.all(8),
    flagStyle: TextStyle(fontSize: 32), // Bigger flag
  ),
)

Country Selector Type

Choose between BottomSheet, Dialog, or Page:

PhoneInputField(
  countrySelectorConfig: const CountrySelectorConfig(
    type: CountrySelectorType.dialog, // or .bottomSheet, .page
    title: 'Choose Country',
    searchHint: 'Search...',
    locale: 'fr', // Show country names in French
  ),
)

Disable Auto-Formatting

Show raw numbers without spaces:

PhoneInputField(
  autoFormat: false,
)

Custom Decoration

Full control over the TextField appearance:

PhoneInputField(
  decoration: InputDecoration(
    labelText: 'Mobile',
    hintText: 'Enter your number',
    prefixIcon: const Icon(Icons.phone),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(12),
    ),
    filled: true,
    fillColor: Colors.grey[100],
  ),
)

πŸ“š Available Countries

Quick Access

// Individual countries (52 African countries)
CountryData.benin
CountryData.nigeria
CountryData.southAfrica
CountryData.kenya
CountryData.ghana
// ... and 47 more African countries

// Non-African
CountryData.france
CountryData.usa
CountryData.canada

Regions

  • Africa: 52 countries (complete coverage)
  • Europe: France, Belgium, Switzerland, Germany, Italy, Spain, Portugal, Netherlands, UnitedKingdom, Ireland,
  • Americas: USA, Canada, Mexico, Brazil, Argentina, Chile, Colombia, Peru, Venezuela, DominicanRepublic

More countries will be added in future releases based on user feedback.

Search Countries

// Search by name (English)
final results = CountryData.search('Benin');

// Search by name (French)
final results = CountryData.search('BΓ©nin', locale: 'fr');

// Get country by code
final benin = CountryData.getByCode('BJ');

// Get country by dial code
final benin = CountryData.getByDialCode('+229');

πŸ”§ API Reference

PhoneInputField

Parameter Type Default Description
controller PhoneController? null Optional controller for programmatic control
initialCountry Country? null Initial country (ignored if controller provided)
countries List<Country> All countries List of available countries
autoDetect bool true Auto-detect user's country
autoFormat bool true Format number as user types
enablePasteDetection bool true Enable intelligent paste detection
keyboardType TextInputType? null Keyboard type (auto-detected if null)
validator Function? null Custom validation function
onChanged Function? null Callback when number changes
onCountryChanged Function? null Callback when country changes
decoration InputDecoration? null TextField decoration
enabled bool true Enable/disable the field
readOnly bool false Make field read-only
style PhoneInputStyle? null Predefined visual style
theme PhoneInputTheme? null Custom theme configuration
showCopyButton bool false Show copy button
copyButtonIcon IconData? Icons.copy Custom copy icon
copiedMessage String? null Custom copied message

PhoneController

Method Description
changeCountry(Country) Change the selected country
updateNumber(String) Update the phone number
clear() Clear the number (keep country)
reset(Country) Reset with new country
Getter Type Description
country Country Current country
nationalNumber String National number (no dial code)
international String Full international number
formatted String Formatted number
isValid bool Is number valid?

PhoneNumber

Property Type Description
country Country The country
nationalNumber String National number
international String International format
formatted String Formatted display
isValid bool Validation status

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Adding More Countries

To add support for additional countries, edit lib/src/core/data/countries_*.dart files.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ’¬ Support

For issues, questions, or feature requests, please open an issue.


Made with ❀️ in Benin πŸ‡§πŸ‡―

Libraries

phone_input_plus