A flutter package which will help you to generate pin code fields with beautiful design and animations. Can be useful for OTP or pin code inputs 🤓🤓
- Automatically focuses the next field on typing and focuses previous field on deletation
- Cursor support ⚡️
- Can be set to any length. (3-6 fields recommended)
- 3 different shapes for text fields
- Highly customizable
- 3 different types of animation for input texts
- Animated active, inactive, selected and disabled field color switching
- Autofocus option
- Otp-code pasting from clipboard
- iOS autofill support
- Error animation. Currently have shake animation only. Watch the example app for how to integrate.
- Works with Flutter's Form. You can use Form validator right off the bat.
- Get currently typed text and use your condition to validate it. (for example: if (currentText.length != 6 || currentText != "your desired code"))
- Haptic Feedback support
- Animated obscure widget support
- Single placeholder text
- To enable "Fill color" for each cells,
enableActiveFillmust be set totrue. The default value isfalse. - To change the keyboard type, for example to use only number keyboard, or only for email use
keyboardTypeparameter, default is [TextInputType.visiblePassword] FocosNodeandTextEditingControllerwill get disposed automatically. UseautoDisposeControllers = falseto disable it.- to use v5.0.0 or above, developers must have Flutter SDK 1.20.0 or above.
- to use v6.0.0 or above, developers must have Flutter SDK 1.22.0 or above.
/// The [BuildContext] of the applicationfinalBuildContext appContext;
///Box Shadow for PincodefinalList<BoxShadow>? boxShadows;
/// length of how many cells there should be. 3-8 is recommended by mefinalint length;
/// you already know what it does i guess :P default is falsefinalbool obscureText;
/// Character used for obscuring text if obscureText is true. /// /// Must not be empty. Single character is recommended. /// /// Default is ● - 'Black Circle' (U+25CF)finalString obscuringCharacter;
/// Widget used to obscure text /// /// it overrides the obscuringCharacterfinalWidget? obscuringWidget;
/// Whether to use haptic feedback or not /// ///finalbool useHapticFeedback;
/// Haptic Feedback Types /// /// heavy, medium, light links to respective impacts /// selection - selectionClick, vibrate - vibrate /// check [HapticFeedback] for morefinalHapticFeedbackTypes hapticFeedbackTypes;
/// Decides whether typed character should be /// briefly shown before being obscuredfinalbool blinkWhenObscuring;
/// Blink Duration if blinkWhenObscuring is set to truefinalDuration blinkDuration;
/// returns the current typed text in the fieldsfinalValueChanged<String> onChanged;
/// returns the typed text when all pins are setfinalValueChanged<String>? onCompleted;
/// returns the typed text when user presses done/next action on the keyboardfinalValueChanged<String>? onSubmitted;
/// the style of the text, default is [ fontSize: 20, fontWeight: FontWeight.bold]finalTextStyle? textStyle;
/// the style of the pasted text, default is [fontWeight: FontWeight.bold] while /// [TextStyle.color] is [ThemeData.colorScheme.onSecondary]finalTextStyle? pastedTextStyle;
/// background color for the whole row of pin code fields.finalColor? backgroundColor;
/// This defines how the elements in the pin code field align. Default to [MainAxisAlignment.spaceBetween]finalMainAxisAlignment mainAxisAlignment;
/// [AnimationType] for the text to appear in the pin code field. Default is [AnimationType.slide]finalAnimationType animationType;
/// Duration for the animation. Default is [Duration(milliseconds: 150)]finalDuration animationDuration;
/// [Curve] for the animation. Default is [Curves.easeInOut]finalCurve animationCurve;
/// [TextInputType] for the pin code fields. default is [TextInputType.visiblePassword]finalTextInputType keyboardType;
/// If the pin code field should be autofocused or not. Default is [false]finalbool autoFocus;
/// Should pass a [FocusNode] to manage it from the parentfinalFocusNode? focusNode;
/// A list of [TextInputFormatter] that goes to the TextFieldfinalList<TextInputFormatter> inputFormatters;
/// Enable or disable the Field. Default is [true]finalbool enabled;
/// [TextEditingController] to control the text manually. Sets a default [TextEditingController()] object if none givenfinalTextEditingController? controller;
/// Enabled Color fill for individual pin fields, default is [false]finalbool enableActiveFill;
/// Auto dismiss the keyboard upon inputting the value for the last field. Default is [true]finalbool autoDismissKeyboard;
/// Auto dispose the [controller] and [FocusNode] upon the destruction of widget from the widget tree. Default is [true]finalbool autoDisposeControllers;
/// Configures how the platform keyboard will select an uppercase or lowercase keyboard. /// Only supports text keyboards, other keyboard types will ignore this configuration. Capitalization is locale-aware. /// - Copied from 'https://api.flutter.dev/flutter/services/TextCapitalization-class.html' /// Default is [TextCapitalization.none]finalTextCapitalization textCapitalization;
finalTextInputAction textInputAction;
/// Triggers the error animationfinalStreamController<ErrorAnimationType>? errorAnimationController;
/// Callback method to validate if text can be pasted. This is helpful when we need to validate text before pasting. /// e.g. validate if text is number. Default will be pasted as received.finalboolFunction(String? text)? beforeTextPaste;
/// Method for detecting a pin_code form tap /// work with all form windowsfinalFunction? onTap;
/// Configuration for paste dialog. Read more [DialogConfig]finalDialogConfig? dialogConfig;
/// Theme for the pin cells. Read more [PinTheme]finalPinTheme pinTheme;
/// Brightness dark or light choices for iOS keyboard.finalBrightness? keyboardAppearance;
/// Validator for the [TextFormField]finalFormFieldValidator<String>? validator;
/// An optional method to call with the final value when the form is saved via /// [FormState.save].finalFormFieldSetter<String>? onSaved;
/// enables auto validation for the [TextFormField] /// Default is [AutovalidateMode.onUserInteraction]finalAutovalidateMode autovalidateMode;
/// The vertical padding from the [PinCodeTextField] to the error text /// Default is 16.finaldouble errorTextSpace;
/// Margin for the error text /// Default is [EdgeInsets.zero].finalEdgeInsets errorTextMargin;
/// [TextDirection] to control a direction in which text flows. /// Default is [TextDirection.ltr]finalTextDirection errorTextDirection;
/// Enables pin autofill for TextFormField. /// Default is truefinalbool enablePinAutofill;
/// Error animation durationfinalint errorAnimationDuration;
/// Whether to show cursor or notfinalbool showCursor;
/// The color of the cursor, default to Theme.of(context).accentColorfinalColor? cursorColor;
/// width of the cursor, default to 2finaldouble cursorWidth;
/// Height of the cursor, default to FontSize + 8;finaldouble? cursorHeight;
/// Autofill cleanup actionfinalAutofillContextAction onAutoFillDisposeAction;
/// Use external [AutoFillGroup]finalbool useExternalAutoFillGroup;
/// Displays a hint or placeholder in the field if it's value is empty. /// It only appears if it's not null. Single character is recommended.finalString? hintCharacter;
/// the style of the [hintCharacter], default is [fontSize: 20, fontWeight: FontWeight.bold] /// and it also uses the [textStyle]'s properties /// [TextStyle.color] is [Colors.grey]finalTextStyle? hintStyle;
/// ScrollPadding follows the same property as TextField's ScrollPadding, default to /// const EdgeInsets.all(20),finalEdgeInsets scrollPadding;
/// Text gradient for PincodefinalGradient? textGradient;
/// Makes the pin cells readOnlyfinalbool readOnly;
/// Enable auto unfocusfinalbool autoUnfocus;
/// Builds separator childrenfinalIndexedWidgetBuilder? separatorBuilder;PinTheme
/// Colors of the input fields which have inputs. Default is [Colors.green]finalColor activeColor;
/// Color of the input field which is currently selected. Default is [Colors.blue]finalColor selectedColor;
/// Colors of the input fields which don't have inputs. Default is [Colors.red]finalColor inactiveColor;
/// Colors of the input fields if the [PinCodeTextField] is disabled. Default is [Colors.grey]finalColor disabledColor;
/// Colors of the input fields which have inputs. Default is [Colors.green]finalColor activeFillColor;
/// Color of the input field which is currently selected. Default is [Colors.blue]finalColor selectedFillColor;
/// Colors of the input fields which don't have inputs. Default is [Colors.red]finalColor inactiveFillColor;
/// Color of the input field when in error mode. Default is [Colors.redAccent]finalColor errorBorderColor;
/// Border radius of each pin code fieldfinalBorderRadius borderRadius;
/// [height] for the pin code field. default is [50.0]finaldouble fieldHeight;
/// [width] for the pin code field. default is [40.0]finaldouble fieldWidth;
/// Border width for the each input fields. Default is [2.0]finaldouble borderWidth;
/// this defines the shape of the input fields. Default is underlinedfinalPinCodeFieldShape shape;DialogConfig
/// title of the [AlertDialog] while pasting the code. Default to [Paste Code]finalString dialogTitle;
/// content of the [AlertDialog] while pasting the code. Default to ["Do you want to paste this code "]finalString dialogContent;
/// Affirmative action text for the [AlertDialog]. Default to "Paste"finalString affirmativeText;
/// Negative action text for the [AlertDialog]. Default to "Cancel"finalString negativeText;
/// The default dialog theme, should it be iOS or other(including web and Android)finalPlatform platform; //enum Platform { iOS, other } other indicates for web and androidThanks to everyone whoever suggested their thoughts to improve this package. And special thanks goes to these people:
The pin code text field widget example
PinCodeTextField(
length:6,
obscureText:false,
animationType:AnimationType.fade,
pinTheme:PinTheme(
shape:PinCodeFieldShape.box,
borderRadius:BorderRadius.circular(5),
fieldHeight:50,
fieldWidth:40,
activeFillColor:Colors.white,
),
animationDuration:Duration(milliseconds:300),
backgroundColor:Colors.blue.shade50,
enableActiveFill:true,
errorAnimationController: errorController,
controller: textEditingController,
onCompleted: (v) {
print("Completed");
},
onChanged: (value) {
print(value);
setState(() {
currentText = value;
});
},
beforeTextPaste: (text) {
print("Allowing to paste $text");
//if you return true then it will show the paste confirmation dialog. Otherwise if false, then nothing will happen.//but you can show anything you want here, like your pop up saying wrong paste format or etcreturntrue;
},
)Shape can be among these 3 types
enumPinCodeFieldShape { box, underline, circle }Animations can be among these 3 types
enumAnimationType { scale, slide, fade, none }Haptic Feedbacks can be among these 5 types
enumHapticFeedbackTypes {
heavy,
light,
medium,
selection,
vibrate,
}
Trigger Error animation
- Create a StreamController
StreamController<ErrorAnimationType> errorController =StreamController<ErrorAnimationType>();- And pass the controller like this.
PinCodeTextField(
length:6,
obscureText:false,
animationType:AnimationType.fade,
animationDuration:Duration(milliseconds:300),
errorAnimationController: errorController, // Pass it here
onChanged: (value) {
setState(() {
currentText = value;
});
},
)- Then you can trigger the animation just by writing this:
errorController.add(ErrorAnimationType.shake); // This will shake the pin code fieldThis full code is from the example folder. You can run the example to see.
classMyAppextendsStatelessWidget {
// This widget is the root of your application.@overrideWidgetbuild(BuildContext context) {
returnMaterialApp(
title:'Flutter Demo',
theme:ThemeData(
primarySwatch:Colors.blue,
),
home:PinCodeVerificationScreen(
"+8801376221100"), // a random number, please don't call xD
);
}
}
classPinCodeVerificationScreenextendsStatefulWidget {
finalString phoneNumber;
PinCodeVerificationScreen(this.phoneNumber);
@override_PinCodeVerificationScreenStatecreateState() =>_PinCodeVerificationScreenState();
}
class_PinCodeVerificationScreenStateextendsState<PinCodeVerificationScreen> {
var onTapRecognizer;
TextEditingController textEditingController =TextEditingController();
// ..text = "123456";StreamController<ErrorAnimationType> errorController;
bool hasError =false;
String currentText ="";
finalGlobalKey<ScaffoldState> scaffoldKey =GlobalKey<ScaffoldState>();
final formKey =GlobalKey<FormState>();
@overridevoidinitState() {
onTapRecognizer =TapGestureRecognizer()
..onTap = () {
Navigator.pop(context);
};
errorController =StreamController<ErrorAnimationType>();
super.initState();
}
@overridevoiddispose() {
errorController.close();
super.dispose();
}
@overrideWidgetbuild(BuildContext context) {
returnScaffold(
backgroundColor:Colors.blue.shade50,
key: scaffoldKey,
body:GestureDetector(
onTap: () {},
child:Container(
height:MediaQuery.of(context).size.height,
width:MediaQuery.of(context).size.width,
child:ListView(
children:<Widget>[
SizedBox(height:30),
Container(
height:MediaQuery.of(context).size.height /3,
child:FlareActor(
"assets/otp.flr",
animation:"otp",
fit:BoxFit.fitHeight,
alignment:Alignment.center,
),
),
SizedBox(height:8),
Padding(
padding:constEdgeInsets.symmetric(vertical:8.0),
child:Text(
'Phone Number Verification',
style:TextStyle(fontWeight:FontWeight.bold, fontSize:22),
textAlign:TextAlign.center,
),
),
Padding(
padding:constEdgeInsets.symmetric(horizontal:30.0, vertical:8),
child:RichText(
text:TextSpan(
text:"Enter the code sent to ",
children: [
TextSpan(
text: widget.phoneNumber,
style:TextStyle(
color:Colors.black,
fontWeight:FontWeight.bold,
fontSize:15)),
],
style:TextStyle(color:Colors.black54, fontSize:15)),
textAlign:TextAlign.center,
),
),
SizedBox(
height:20,
),
Form(
key: formKey,
child:Padding(
padding:constEdgeInsets.symmetric(
vertical:8.0, horizontal:30),
child:PinCodeTextField(
appContext: context,
pastedTextStyle:TextStyle(
color:Colors.green.shade600,
fontWeight:FontWeight.bold,
),
length:6,
obscureText:false,
obscuringCharacter:'*',
animationType:AnimationType.fade,
validator: (v) {
if (v.length <3) {
return"I'm from validator";
} else {
returnnull;
}
},
pinTheme:PinTheme(
shape:PinCodeFieldShape.box,
borderRadius:BorderRadius.circular(5),
fieldHeight:60,
fieldWidth:50,
activeFillColor:
hasError ?Colors.orange :Colors.white,
),
cursorColor:Colors.black,
animationDuration:Duration(milliseconds:300),
textStyle:TextStyle(fontSize:20, height:1.6),
backgroundColor:Colors.blue.shade50,
enableActiveFill:true,
errorAnimationController: errorController,
controller: textEditingController,
keyboardType:TextInputType.number,
boxShadows: [
BoxShadow(
offset:Offset(0, 1),
color:Colors.black12,
blurRadius:10,
)
],
onCompleted: (v) {
print("Completed");
},
// onTap: () {// print("Pressed");// },
onChanged: (value) {
print(value);
setState(() {
currentText = value;
});
},
beforeTextPaste: (text) {
print("Allowing to paste $text");
//if you return true then it will show the paste confirmation dialog. Otherwise if false, then nothing will happen.//but you can show anything you want here, like your pop up saying wrong paste format or etcreturntrue;
},
)),
),
Padding(
padding:constEdgeInsets.symmetric(horizontal:30.0),
child:Text(
hasError ?"*Please fill up all the cells properly":"",
style:TextStyle(
color:Colors.red,
fontSize:12,
fontWeight:FontWeight.w400),
),
),
SizedBox(
height:20,
),
RichText(
textAlign:TextAlign.center,
text:TextSpan(
text:"Didn't receive the code? ",
style:TextStyle(color:Colors.black54, fontSize:15),
children: [
TextSpan(
text:" RESEND",
recognizer: onTapRecognizer,
style:TextStyle(
color:Color(0xFF91D3B3),
fontWeight:FontWeight.bold,
fontSize:16))
]),
),
SizedBox(
height:14,
),
Container(
margin:constEdgeInsets.symmetric(vertical:16.0, horizontal:30),
child:ButtonTheme(
height:50,
child:FlatButton(
onPressed: () {
formKey.currentState.validate();
// conditions for validatingif (currentText.length !=6|| currentText !="towtow") {
errorController.add(ErrorAnimationType
.shake); // Triggering error shake animationsetState(() {
hasError =true;
});
} else {
setState(() {
hasError =false;
scaffoldKey.currentState.showSnackBar(SnackBar(
content:Text("Aye!!"),
duration:Duration(seconds:2),
));
});
}
},
child:Center(
child:Text(
"VERIFY".toUpperCase(),
style:TextStyle(
color:Colors.white,
fontSize:18,
fontWeight:FontWeight.bold),
)),
),
),
decoration:BoxDecoration(
color:Colors.green.shade300,
borderRadius:BorderRadius.circular(5),
boxShadow: [
BoxShadow(
color:Colors.green.shade200,
offset:Offset(1, -2),
blurRadius:5),
BoxShadow(
color:Colors.green.shade200,
offset:Offset(-1, 2),
blurRadius:5)
]),
),
SizedBox(
height:16,
),
Row(
mainAxisAlignment:MainAxisAlignment.center,
children:<Widget>[
FlatButton(
child:Text("Clear"),
onPressed: () {
textEditingController.clear();
},
),
FlatButton(
child:Text("Set Text"),
onPressed: () {
textEditingController.text ="123456";
},
),
],
)
],
),
),
),
);
}
}







