AI prompts
base on # Intl Phone Number Input
A simple and customizable flutter package for inputting phone number in intl / international format uses Google's libphonenumber
| CustomDecoration | CustomBorder | Default |
|----------|-------------|--------|
| <img src="https://user-images.githubusercontent.com/27495055/80114512-9544b100-857b-11ea-9292-9c9c3eaf93e0.png" width="240" height="430" alt="Screenshot_1587652933"/> | <img src="https://user-images.githubusercontent.com/27495055/80115521-beb20c80-857c-11ea-9902-41c444a3bd33.png" width="240" height="430" alt="Screenshot_1587652933"/> | <img src="https://user-images.githubusercontent.com/27495055/80116034-63344e80-857d-11ea-9922-1062b4320503.png" width="240" height="430" alt="Screenshot_1587652933"/> |
| Web |
|-----|
| <img src="https://user-images.githubusercontent.com/27495055/103301956-c9257f80-4a02-11eb-8385-01564c2ec875.png" width=420 height=550></img> |
### What's new
- Replace libphonenumber_plugin with dlibphonenumber
- Updated libphonenumber and PhoneNumberToCarrierMapper on Android
- Removed dependency on libphonenumber
- Switch from libphonenumber-iOS to PhoneNumberKit on iOS
- Update libphonenumber.js file
- Depreciating getNameForNumber in future updates
### Features
- Support all Flutter platforms.
- Support for RTL languages
- Selector mode dropdown, bottom sheet and dialog
- As You Type Formatter: formats inputs to its selected international format
- Get Region Info with PhoneNumber.getRegionInfoFromPhoneNumber(String phoneNumber, [String isoCode]);
- Format PhoneNumber with PhoneNumber.getParsableNumber(String phoneNumber, String isoCode) or `PhoneNumber Reference`.parseNumber()
- Custom list of countries e.g. ['NG', 'GH', 'BJ' 'TG', 'CI']
```dart
String phoneNumber = '+234 500 500 5005';
PhoneNumber number = await PhoneNumber.getRegionInfoFromPhoneNumber(phoneNumber);
String parsableNumber = number.parseNumber();
`controller reference`.text = parsableNumber
```
### Note
``` dart
PhoneNumber.getRegionInfoFromPhoneNumber(String phoneNumber, [String isoCode])
```
> Could throw an Exception if the phoneNumber isn't recognised its a good pattern to pass the country's isoCode or have '+' at the beginning of the string
> isoCode could be null if PhoneNumber is not recognised
# Usage
### Constructors
| s/n | Constructor |
| --- | ------------------------------------------------------- |
| 1 | InternationalPhoneNumberInput |
## Available Parameters
```dart
InternationalPhoneNumberInput({
Key key,
this.selectorConfig = const SelectorConfig(),
@required this.onInputChanged,
this.onInputValidated,
this.onSubmit,
this.onFieldSubmitted,
this.validator,
this.onSaved,
this.textFieldController,
this.keyboardAction,
this.keyboardType = TextInputType.phone,
this.initialValue,
this.hintText = 'Phone number',
this.errorMessage = 'Invalid phone number',
this.selectorButtonOnErrorPadding = 24,
this.spaceBetweenSelectorAndTextField = 12,
this.maxLength = 15,
this.isEnabled = true,
this.formatInput = true,
this.autoFocus = false,
this.autoFocusSearch = false,
this.autoValidateMode = AutovalidateMode.disabled,
this.ignoreBlank = false,
this.countrySelectorScrollControlled = true,
this.locale,
this.textStyle,
this.selectorTextStyle,
this.inputBorder,
this.inputDecoration,
this.searchBoxDecoration,
this.textAlign = TextAlign.start,
this.textAlignVertical = TextAlignVertical.center,
this.scrollPadding = const EdgeInsets.all(20.0),
this.focusNode,
this.cursorColor,
this.autofillHints,
this.countries
});
```
```dart
SelectorConfig({
this.selectorType = PhoneInputSelectorType.DROPDOWN,
this.showFlags = true,
this.useEmoji = false,
this.backgroundColor,
this.countryComparator,
this.setSelectorButtonAsPrefixIcon = false,
this.useBottomSheetSafeArea = false,
});
```
| Parameter | Datatype | Initial Value |
|--------------------------------- |------------------------|---------------------------|
| onInputChanged | function(PhoneNumber) | null |
| onSaved | function(PhoneNumber) | null |
| onInputValidated | function(bool) | null |
| focusNode | FocusNode | null |
| textFieldController | TextEditingController | TextEditingController() |
| onSubmit | Function() | null |
| keyboardAction | TextInputAction | null |
| keyboardType | TextInputType | TextInputType.phone |
| countries | List<string> | null |
| textStyle | TextStyle | null |
| selectorTextStyle | TextStyle | null |
| inputBorder | InputBorder | null |
| inputDecoration | InputDecoration | null |
| initialValue | PhoneNumber | null |
| hintText | String | Phone Number |
| selectorButtonOnErrorPadding | double | 24 |
| spaceBetweenSelectorAndTextField | double | 12 |
| maxLength | integer | 15 |
| isEnabled | boolean | true |
| autoFocus | boolean | false |
| autoValidateMode | AutoValidateMode | AutoValidateMode.disabled |
| formatInput | boolean | true |
| errorMessage | String | Invalid phone number |
| selectorConfig | SelectorConfig | SelectorConfig() |
| ignoreBlank | boolean | false |
| locale | String | null |
| searchBoxDecoration | InputDecoration | null |
| textAlign | TextAlign | TextAlign.start |
| textAlignVertical | TextAlignVertical | TextAlignVertical.center |
| scrollPadding | EdgeInsets | EdgeInsets.all(20.0) |
| countrySelectorScrollControlled | boolean | true |
| cursorColor | String \ | null |
| autofillHints | Iterable<String> | null |
### Selector Types
| DROPDOWN | BOTTOMSHEET | DIALOG |
|----------|-------------|--------|
| <img src="https://user-images.githubusercontent.com/27495055/80116593-10a76200-857e-11ea-9600-f2cfef5b2965.png" height="430" alt="Screenshot_1587652933"/> | <img src="https://user-images.githubusercontent.com/27495055/80116677-261c8c00-857e-11ea-8167-a3de563287f4.png" width="240" height="430" alt="Screenshot_1587652933"/> | <img src="https://user-images.githubusercontent.com/27495055/80116721-3896c580-857e-11ea-84da-4efe13011d50.png" width="240" height="430" alt="Screenshot_1587652933"/> |
## Advanced Usage Examples
### Basic Setup
```dart
class MyPhoneForm extends StatefulWidget {
@override
_MyPhoneFormState createState() => _MyPhoneFormState();
}
class _MyPhoneFormState extends State<MyPhoneForm> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController controller = TextEditingController();
String initialCountry = 'NG';
PhoneNumber number = PhoneNumber(isoCode: 'NG');
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
InternationalPhoneNumberInput(
onInputChanged: (PhoneNumber number) {
print(number.phoneNumber);
},
onInputValidated: (bool value) {
print(value);
},
selectorConfig: SelectorConfig(
selectorType: PhoneInputSelectorType.BOTTOM_SHEET,
backgroundColor: Colors.black87,
),
ignoreBlank: false,
autoValidateMode: AutovalidateMode.disabled,
selectorTextStyle: TextStyle(color: Colors.black),
initialValue: number,
textFieldController: controller,
formatInput: false,
keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
inputBorder: OutlineInputBorder(),
onSaved: (PhoneNumber number) {
print('On Saved: $number');
},
),
ElevatedButton(
onPressed: () {
formKey.currentState?.validate();
},
child: Text('Validate'),
),
ElevatedButton(
onPressed: () {
getPhoneNumber(controller.text);
},
child: Text('Update'),
),
],
),
),
);
}
void getPhoneNumber(String phoneNumber) async {
PhoneNumber number = await PhoneNumber.getRegionInfoFromPhoneNumber(phoneNumber, 'US');
setState(() {
this.number = number;
});
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
}
```
### Custom Styling
```dart
InternationalPhoneNumberInput(
onInputChanged: (PhoneNumber number) {},
selectorConfig: SelectorConfig(
selectorType: PhoneInputSelectorType.DROPDOWN,
backgroundColor: Theme.of(context).backgroundColor,
setSelectorButtonAsPrefixIcon: true,
leadingPadding: 20,
trailingSpace: false,
),
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
selectorTextStyle: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
),
inputDecoration: InputDecoration(
labelText: 'Phone Number',
labelStyle: TextStyle(color: Colors.blue),
hintText: 'Enter your phone number',
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.blue, width: 2),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
spaceBetweenSelectorAndTextField: 16,
)
```
### Country Filtering
```dart
// Only allow North American countries
InternationalPhoneNumberInput(
onInputChanged: (PhoneNumber number) {},
countries: ['US', 'CA', 'MX'],
selectorConfig: SelectorConfig(
selectorType: PhoneInputSelectorType.DIALOG,
),
)
// Only allow European countries
InternationalPhoneNumberInput(
onInputChanged: (PhoneNumber number) {},
countries: ['GB', 'FR', 'DE', 'IT', 'ES', 'NL'],
selectorConfig: SelectorConfig(
selectorType: PhoneInputSelectorType.BOTTOM_SHEET,
countryComparator: (Country a, Country b) => a.name!.compareTo(b.name!),
),
)
```
### Form Validation
```dart
class PhoneNumberForm extends StatefulWidget {
@override
_PhoneNumberFormState createState() => _PhoneNumberFormState();
}
class _PhoneNumberFormState extends State<PhoneNumberForm> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
PhoneNumber? _phoneNumber;
bool _isValidNumber = false;
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
InternationalPhoneNumberInput(
onInputChanged: (PhoneNumber number) {
_phoneNumber = number;
},
onInputValidated: (bool value) {
setState(() {
_isValidNumber = value;
});
},
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Please enter a phone number';
}
if (!_isValidNumber) {
return 'Please enter a valid phone number';
}
return null;
},
autoValidateMode: AutovalidateMode.onUserInteraction,
errorMessage: 'Invalid phone number',
inputDecoration: InputDecoration(
labelText: 'Phone Number *',
helperText: 'Required field',
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Phone number: ${_phoneNumber?.phoneNumber}')),
);
}
},
child: Text('Submit'),
),
],
),
);
}
}
```
### Working with Controllers
```dart
class ControllerExample extends StatefulWidget {
@override
_ControllerExampleState createState() => _ControllerExampleState();
}
class _ControllerExampleState extends State<ControllerExample> {
final TextEditingController _phoneController = TextEditingController();
PhoneNumber? _currentNumber;
@override
Widget build(BuildContext context) {
return Column(
children: [
InternationalPhoneNumberInput(
onInputChanged: (PhoneNumber number) {
_currentNumber = number;
},
textFieldController: _phoneController,
formatInput: true,
keyboardType: TextInputType.phone,
),
SizedBox(height: 20),
Row(
children: [
ElevatedButton(
onPressed: () {
_phoneController.text = '5551234567';
},
child: Text('Set US Number'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
_phoneController.clear();
},
child: Text('Clear'),
),
],
),
SizedBox(height: 10),
Text('Current: ${_currentNumber?.phoneNumber ?? 'None'}'),
],
);
}
@override
void dispose() {
_phoneController.dispose();
super.dispose();
}
}
```
## Troubleshooting
### Common Issues
#### Issue: "Invalid phone number" appears for valid numbers
**Solution:**
- Ensure the phone number includes the country code
- Use the `initialValue` parameter to set the correct country
- Check that the `isoCode` matches the phone number's country
```dart
// Correct way to set initial value
PhoneNumber initialValue = PhoneNumber(
phoneNumber: '+1234567890', // Include country code
isoCode: 'US',
dialCode: '+1',
);
```
#### Issue: Country selector not opening
**Solution:**
- Make sure the widget is wrapped in a `MaterialApp` or `CupertinoApp`
- Check for any `GestureDetector` conflicts in parent widgets
- Verify that `isEnabled` is set to `true`
#### Issue: TextFormField validation not working
**Solution:**
- Wrap the widget in a `Form` widget
- Use `autoValidateMode` for real-time validation
- Implement both `validator` and `onInputValidated` callbacks
```dart
Form(
child: InternationalPhoneNumberInput(
autoValidateMode: AutovalidateMode.onUserInteraction,
validator: (String? value) {
// Your validation logic
return null; // Return null if valid
},
onInputValidated: (bool isValid) {
// Handle validation state
},
),
)
```
#### Issue: Flag images not loading
**Solution:**
- Ensure you have added the assets to your `pubspec.yaml`:
```yaml
flutter:
assets:
- packages/intl_phone_number_input/assets/flags/
```
- Alternative: Use emoji flags instead:
```dart
SelectorConfig(
useEmoji: true,
showFlags: true,
)
```
#### Issue: Layout overflow in bottom sheet
**Solution:**
- Set `useBottomSheetSafeArea: true`
- Adjust `countrySelectorScrollControlled`
- Use a smaller device or test on different screen sizes
```dart
SelectorConfig(
selectorType: PhoneInputSelectorType.BOTTOM_SHEET,
useBottomSheetSafeArea: true,
countrySelectorScrollControlled: true,
)
```
#### Issue: Performance issues with large country lists
**Solution:**
- Filter countries using the `countries` parameter
- Use pagination or search functionality in custom implementations
```dart
// Only show commonly used countries
InternationalPhoneNumberInput(
countries: ['US', 'GB', 'CA', 'AU', 'FR', 'DE'],
// ... other parameters
)
```
### Migration Guide
#### From version 0.7.x to current
- Update your `pubspec.yaml` to use the latest version
- Replace deprecated `getNameForNumber` calls (removed in 0.7.3)
- Update any custom `backgroundColor` usage in `SelectorConfig` (deprecated in 0.7.0)
#### Breaking Changes in 0.7.0
- `SelectorConfig.backgroundColor` deprecated - use theme colors instead
- Null safety migration - update null checks in your code
- Some internal API changes for better performance
### Performance Tips
1. **Limit Country List**: Use the `countries` parameter to show only relevant countries
2. **Optimize Flags**: Use `useEmoji: true` for better performance
3. **Lazy Loading**: Enable `countrySelectorScrollControlled` for large lists
4. **Debounce Input**: Implement debouncing for `onInputChanged` if making API calls
### Accessibility
- The widget supports screen readers out of the box
- Use `autofillHints` for better form completion
- Provide meaningful `inputDecoration.labelText` and `hintText`
- Test with TalkBack (Android) and VoiceOver (iOS)
### Testing
Widget Key parameters and Helper classes are now available for integration testing check out this example 🎯 [Integration Testing Example](https://gist.github.com/natintosh/b7b40d75240a65fdb63942a4b36753e5)
### Testing Helper Keys
```dart
// Use these keys for integration testing
find.byValueKey(TestHelper.TextInputKeyValue) // Text input field
find.byValueKey(TestHelper.DropdownButtonKeyValue) // Country selector
find.byValueKey(TestHelper.CountrySearchInputKeyValue) // Search field in popup
find.byValueKey(TestHelper.countryItemKeyValue('US')) // Specific country item
```
# Contributions
If you encounter any problem or the library is missing a feature feel free to open an issue. Feel free to fork, improve the package and make pull request.
## Co-contributors
Interested in becoming a co-contributors checkout this link for more info [discussions/201](https://github.com/natintosh/intl_phone_number_input/discussions/201)
# Contributors
<a href="https://github.com/natintosh/intl_phone_number_input/graphs/contributors">
<img src="https://contrib.rocks/image?repo=natintosh/intl_phone_number_input" />
</a>
Made with [contributors-img](https://contributors-img.web.app).
# Dependencies
* [dlibphonenumber](https://pub.dev/packages/dlibphonenumber)
* [equatable](https://pub.dev/packages/equatable)
# Credits
A special thanks to [niinyarko](https://github.com/niinyarko/flutter-international-phone-input)
# FAQ
* For discussions and frequent question and concerns, check [here](https://github.com/natintosh/intl_phone_number_input/discussions/159)
", Assign "at most 3 tags" to the expected json: {"id":"11891","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"