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:
- Exact matches - Country name or code exactly matches query
- Starts with - Country name or code starts with query
- 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:
-
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 -
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 } -
Update supported locales:
static const List<Locale> supportedLocales = <Locale>[ Locale('en'), // Remove unused locales // Locale('es'), // Locale('fr'), // Locale('ru'), ]; -
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.