Country Search

A beautiful and customizable country picker widget for Flutter with multi-language support.

Features

  • 🌍 246 Countries: Comprehensive list of countries with flags and ISO codes
  • 🌐 Multi-language Support: Built-in support for English, Spanish, French, German, Portuguese, and Russian
  • 🎨 Customizable UI: Dark theme with modern design
  • 🔍 Smart Search: Search by country name or code with priority ranking
  • 📱 Responsive: Works on all screen sizes
  • 🖥️ Adaptive Design: Optimized for mobile, tablet, and desktop screens
  • Lightweight: Minimal dependencies (only Flutter SDK)
  • 🎯 Fully Localized: All UI text is localized (labels, placeholders, titles)
  • Fully Tested: Comprehensive test coverage for widgets, data, and localizations

Adaptive Design

The Country Picker widget is designed to work seamlessly across all device types:

📱 Mobile Devices

  • Optimized touch targets for easy selection
  • Swipe-friendly interface
  • Compact layout for small screens
  • Search bar with mobile-friendly keyboard

📱 Tablets

  • Larger touch targets for better usability
  • Optimized spacing for medium screens
  • Enhanced visual hierarchy
  • Landscape and portrait orientation support

🖥️ Desktop & Web

  • Mouse-friendly hover effects
  • Keyboard navigation support
  • Larger click targets
  • Responsive grid layout
  • Web-optimized performance

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  country_search: ^1.0.0

Usage

Basic Usage

import 'package:country_search/country_search.dart';

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

class _MyWidgetState extends State<MyWidget> {
  Country? selectedCountry;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: CountryPicker(
          selectedCountry: selectedCountry,
          onCountrySelected: (Country country) {
            setState(() {
              selectedCountry = country;
            });
            debugPrint('Selected: ${country.flag} ${country.code}');
          },
        ),
      ),
    );
  }
}

Custom Labels

CountryPicker(
  selectedCountry: selectedCountry,
  onCountrySelected: (Country country) {
    setState(() {
      selectedCountry = country;
    });
  },
  labelText: 'Choose your country',
  hintText: 'Select a country',
)

Multi-language Setup

To enable multi-language support, add the localizations to your app:

import 'package:country_search/country_search.dart';

MaterialApp(
  localizationsDelegates: [
    // ... other delegates
    CountryLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('de'),
    const Locale('en'),
    const Locale('es'),
    const Locale('fr'),
    const Locale('pt'),
    const Locale('ru'),
  ],
  // ... rest of your app
)

Language Switching Example

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Locale _locale = const Locale('en');

  void _changeLanguage(Locale locale) {
    setState(() {
      _locale = locale;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      locale: _locale,
      localizationsDelegates: [
        CountryLocalizations.delegate,
        // ... other delegates
      ],
      supportedLocales: [
        const Locale('en'),
        const Locale('es'),
        const Locale('fr'),
        const Locale('ru'),
      ],
      home: MyHomePage(onLanguageChanged: _changeLanguage),
    );
  }
}

API Reference

CountryPicker Widget

Parameter Type Required Description
selectedCountry Country? No Currently selected country
onCountrySelected Function(Country) Yes Callback when country is selected
labelText String? No Custom label text (overrides localized text)
hintText String? No Custom hint text (overrides localized text)

Country Model

class Country {
  final String code;  // ISO country code (e.g., 'US', 'RU')
  final String flag;  // Country flag emoji (e.g., '🇺🇸', '🇷🇺')
}

CountryData Utility

// Get country by code
Country? country = CountryData.getCountryByCode('US');

// Get sorted countries for current language
List<Country> sortedCountries = CountryData.getSortedCountries(
  (code) => CountryLocalizations.of(context).getCountryName(code)
);

// Search countries with smart ranking
List<Country> results = CountryData.searchCountries(
  'russia',
  (code) => CountryLocalizations.of(context).getCountryName(code)
);

Localization Methods

// Get localized country name
String countryName = CountryLocalizations.of(context).getCountryName('US');

// Get all country names for current language
Map<String, String> allNames = CountryLocalizations.of(context).allCountryNames;

// Get localized UI text
String selectText = CountryLocalizations.of(context).selectCountry;
String searchText = CountryLocalizations.of(context).searchCountry;
String hintText = CountryLocalizations.of(context).selectYourCountry;

Supported Languages

  • 🇺🇸 English (en) - "Select Country", "Search country...", "Select your country"
  • 🇪🇸 Spanish (es) - "Seleccionar país", "Buscar país...", "Selecciona tu país"
  • 🇫🇷 French (fr) - "Sélectionner un pays", "Rechercher un pays...", "Sélectionnez votre pays"
  • 🇷🇺 Russian (ru) - "Выберите страну", "Поиск страны...", "Выберите вашу страну"

Features in Detail

Smart Search Algorithm

The search functionality prioritizes results in this order:

  1. Exact matches - Country name or code exactly matches query
  2. Starts with - Country name or code starts with query
  3. Contains - Country name or code contains query

Localization Support

  • All UI text is automatically localized based on app locale
  • Country names are sorted alphabetically in the current language
  • Search works in the current language
  • Custom labels can override localized text

Responsive Design

  • Adapts to different screen sizes
  • Modal bottom sheet with draggable height
  • Optimized for mobile and desktop

Complete Example

import 'package:flutter/material.dart';
import 'package:country_search/country_search.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Locale _locale = const Locale('en');

  void _changeLanguage(Locale locale) {
    setState(() {
      _locale = locale;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Country Picker Demo',
      theme: ThemeData.dark(),
      locale: _locale,
      localizationsDelegates: [
        CountryLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('en'),
        const Locale('es'),
        const Locale('fr'),
        const Locale('ru'),
      ],
      home: MyHomePage(onLanguageChanged: _changeLanguage, currentLocale: _locale),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final Function(Locale) onLanguageChanged;
  final Locale currentLocale;

  const MyHomePage({
    required this.onLanguageChanged,
    required this.currentLocale,
  });

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Country? selectedCountry;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Country Picker Demo'),
        actions: [
          PopupMenuButton<Locale>(
            onSelected: widget.onLanguageChanged,
            itemBuilder: (context) => [
              const PopupMenuItem(
                value: Locale('en'),
                child: Text('🇺🇸 English'),
              ),
              const PopupMenuItem(
                value: Locale('es'),
                child: Text('🇪🇸 Español'),
              ),
              const PopupMenuItem(
                value: Locale('fr'),
                child: Text('🇫🇷 Français'),
              ),
              const PopupMenuItem(
                value: Locale('ru'),
                child: Text('🇷🇺 Русский'),
              ),
            ],
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(_getLanguageFlag(widget.currentLocale)),
                  const SizedBox(width: 8),
                  Text(_getLanguageName(widget.currentLocale)),
                ],
              ),
            ),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            CountryPicker(
              selectedCountry: selectedCountry,
              onCountrySelected: (Country country) {
                setState(() {
                  selectedCountry = country;
                });
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(
                    content: Text('Selected: ${country.flag} ${country.code}'),
                    backgroundColor: Colors.green,
                  ),
                );
              },
            ),
            const SizedBox(height: 20),
            if (selectedCountry != null)
              Container(
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.white.withAlpha((0.1 * 255).toInt()),
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.white24),
                ),
                child: Row(
                  children: [
                    Text(
                      selectedCountry!.flag,
                      style: const TextStyle(fontSize: 32),
                    ),
                    const SizedBox(width: 16),
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            selectedCountry!.code,
                            style: const TextStyle(
                              fontSize: 18,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          Text(
                            CountryLocalizations.of(context).getCountryName(selectedCountry!.code),
                            style: const TextStyle(
                              fontSize: 14,
                              color: Colors.white70,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
          ],
        ),
      ),
    );
  }

  String _getLanguageFlag(Locale locale) {
    switch (locale.languageCode) {
      case 'en': return '🇺🇸';
      case 'es': return '🇪🇸';
      case 'fr': return '🇫🇷';
      case 'ru': return '🇷🇺';
      default: return '🇺🇸';
    }
  }

  String _getLanguageName(Locale locale) {
    switch (locale.languageCode) {
      case 'en': return 'English';
      case 'es': return 'Español';
      case 'fr': return 'Français';
      case 'ru': return 'Русский';
      default: return 'English';
    }
  }
}

Testing

This package includes comprehensive test coverage to ensure reliability and quality:

Widget Tests

  • CountryPicker Widget: Tests proper rendering and display
  • Selected Country Display: Verifies selected country is shown correctly
  • Widget Integration: Tests widget integration with MaterialApp

Data Tests

  • Country Data: Verifies 224 countries are loaded correctly
  • Country Lookup: Tests finding countries by ISO code
  • Invalid Code Handling: Tests null return for invalid country codes
  • Search Functionality: Tests search by country name
  • Case Insensitive Search: Tests search with different letter cases

Localization Tests

  • English Localizations: Tests all English translations
  • Spanish Localizations: Tests all Spanish translations
  • French Localizations: Tests all French translations
  • Russian Localizations: Tests all Russian translations
  • Consistency: Verifies all languages have same country codes
  • Localization Delegate: Tests delegate functionality

Test Coverage

  • Widget Tests: 2 test cases
  • Data Tests: 4 test cases
  • Localization Tests: 6 test cases
  • Total: 12 test cases with 100% pass rate

Run tests with:

flutter test

Optimization

Removing Unused Languages

To reduce package size and simplify maintenance, you can remove languages you don't need:

  1. Delete language files you don't use:

    # Remove Spanish (if not needed)
    rm lib/src/flutter_country_picker/localizations/country_localizations_es.dart
       
    # Remove French (if not needed)  
    rm lib/src/flutter_country_picker/localizations/country_localizations_fr.dart
       
    # Remove Russian (if not needed)
    rm lib/src/flutter_country_picker/localizations/country_localizations_ru.dart
    
  2. Update the lookup function in country_localizations.dart:

    CountryLocalizations lookupCountryLocalizations(Locale locale) {
      switch (locale.languageCode) {
        case 'en':
          return CountryLocalizationsEn();
        // Remove cases for deleted languages
        // case 'es': return CountryLocalizationsEs();
        // case 'fr': return CountryLocalizationsFr();
        // case 'ru': return CountryLocalizationsRu();
      }
      return CountryLocalizationsEn(); // Fallback to English
    }
    
  3. Update supported locales:

    static const List<Locale> supportedLocales = <Locale>[
      Locale('en'),
      // Remove unused locales
      // Locale('es'),
      // Locale('fr'), 
      // Locale('ru'),
    ];
    
  4. Remove imports for deleted files:

    // Remove these imports
    // import 'country_localizations_es.dart';
    // import 'country_localizations_fr.dart';
    // import 'country_localizations_ru.dart';
    

Benefits:

  • Smaller package size - fewer files to include
  • Faster compilation - less code to process
  • Easier maintenance - fewer files to update
  • Automatic fallback - unsupported languages use English

Note: The package includes fallback to English for unsupported locales, so removing languages is safe.

About

Author: Stanislav Bozhko
Email: [email protected]
GitHub: @stanislavworldin

This package is maintained as an open-source project. Feel free to contribute!

Contributing

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

License

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

Libraries