Open In App

Flutter – OTP Input Fields

Modern applications majorly that require OTP verification require OTP style input fields. We need to generate larger boilerplate code for creating amazing input fields. Then, the pinput package comes to the rescue. With this package, we can create customizable OTP input fields easily. Let us see its implementation in this article.

Step 1: Add the dependency



To use pinput, we need to add pinput in pubspec.yaml file of the app.

flutter pub add pinput

Step 2: Import the dependency



Import the dependency in the file where we need to create input fields.




import 'package:pinput/pin_put/pin_put.dart';

Step 3: Implementation




void _showSnackBar(String pin) {
    final snackBar = SnackBar(
      duration: Duration(seconds: 4),
      content: Container(
        height: 80.0,
        child: Center(
          child: Text(
            'Pin Submitted: $pin',
            style: TextStyle(fontSize: 25.0),
          ),
        ),
      ),
      backgroundColor: Colors.green,
    );
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(snackBar);
  }




final _pinPutController = TextEditingController();




PinPut({
  Key? key,
  required int fieldsCount,
  void Function(String)? onSubmit,
  void Function(String?)? onSaved,
  void Function(String)? onChanged,
  void Function()? onTap,
  void Function(String?)? onClipboardFound,
  TextEditingController? controller,
  FocusNode? focusNode,
  Widget? preFilledWidget,
  List<int> separatorPositions = const [],
  Widget separator = const SizedBox(width: 15.0),
  TextStyle? textStyle,
  BoxDecoration? submittedFieldDecoration,
  BoxDecoration? selectedFieldDecoration,
  BoxDecoration? followingFieldDecoration,
  BoxDecoration? disabledDecoration,
  double? eachFieldWidth,
  double? eachFieldHeight,
  MainAxisAlignment fieldsAlignment = MainAxisAlignment.spaceBetween,
  AlignmentGeometry eachFieldAlignment = Alignment.center,
  EdgeInsetsGeometry? eachFieldMargin,
  EdgeInsetsGeometry? eachFieldPadding,
  BoxConstraints eachFieldConstraints = const BoxConstraints(minHeight: 40.0,
                                                             minWidth: 40.0),
  InputDecoration? inputDecoration,
  Curve animationCurve = Curves.linear,
  Duration animationDuration = const Duration(milliseconds: 160),
  PinAnimationType pinAnimationType = PinAnimationType.slide,
  Offset? slideTransitionBeginOffset,
  bool enabled = true,
  bool checkClipboard = false,
  bool useNativeKeyboard = true,
  bool autofocus = false,
  AutovalidateMode autovalidateMode = AutovalidateMode.disabled,
  bool withCursor = false,
  Widget? cursor,
  Brightness? keyboardAppearance,
  List<TextInputFormatter>? inputFormatters,
  String? Function(String?)? validator,
  TextInputType keyboardType = TextInputType.number,
  String? obscureText,
  TextCapitalization textCapitalization = TextCapitalization.none,
  TextInputAction? textInputAction,
  ToolbarOptions? toolbarOptions = const ToolbarOptions(paste: true),
  MainAxisSize mainAxisSize = MainAxisSize.max,
  Iterable<String>? autofillHints,
  bool enableIMEPersonalizedLearning = true,
  String? initialValue,
  SmartDashesType? smartDashesType,
  SmartQuotesType? smartQuotesType,
  bool enableSuggestions = true,
  MaxLengthEnforcement? maxLengthEnforcement,
  void Function()? onEditingComplete,
  double cursorWidth = 2,
  double? cursorHeight,
  Radius? cursorRadius,
  Color? cursorColor,
  bool enableInteractiveSelection = true,
  TextSelectionControls? selectionControls,
  Widget? Function(BuildContext, {required int currentLength,
                                  required bool isFocused,
                                  required int? maxLength})? buildCounter,
  String? restorationId
  })

Let us see two different styles of input fields through examples.

DarkRounded Fields:




Widget darkRoundedPinPut() {
    return PinPut(
      eachFieldWidth: 50.0,
      eachFieldHeight: 50.0,
      withCursor: true,
      fieldsCount: 5,
      controller: _pinPutController,
      eachFieldMargin: EdgeInsets.symmetric(horizontal: 10),
      onSubmit: (String pin) => _showSnackBar(pin),
      submittedFieldDecoration: BoxDecoration(
        color: Colors.green[800],
        borderRadius: BorderRadius.circular(15.0),
      ),
      selectedFieldDecoration: BoxDecoration(
        color: Colors.green[800],
        borderRadius: BorderRadius.circular(15.0),
      ),
      followingFieldDecoration: BoxDecoration(
        color: Colors.green[800],
        borderRadius: BorderRadius.circular(15.0),
      ),
      pinAnimationType: PinAnimationType.rotation,
      textStyle: TextStyle(color: Colors.white,
                           fontSize: 20.0,
                           height: 1),
    );
  }

Output:

Fields with AnimatedBorder:




Widget animatedBorders() {
  return Padding(
    padding: const EdgeInsets.all(8.0),
    child: PinPut(
      fieldsCount: 4,
      eachFieldHeight: 50.0,
      withCursor: true,
      onSubmit: (String pin) => _showSnackBar(pin),
      controller: _pinPutController,
      submittedFieldDecoration: BoxDecoration(
        border: Border.all(color: Colors.black),
        borderRadius: BorderRadius.circular(15.0),
      ).copyWith(
        borderRadius: BorderRadius.circular(20.0),
      ),
      selectedFieldDecoration: BoxDecoration(
        color: Colors.green,
        border: Border.all(color: Colors.black),
        borderRadius: BorderRadius.circular(15.0),
      ),
      followingFieldDecoration: BoxDecoration(
        border: Border.all(color: Colors.black),
        borderRadius: BorderRadius.circular(15.0),
      ).copyWith(
        borderRadius: BorderRadius.circular(5.0),
        border: Border.all(
          color: Colors.black,
        ),
      ),
    ),
  );
}

Output:

Full Source Code:




import 'package:flutter/material.dart';
import 'package:pinput/pin_put/pin_put.dart';
  
void main() => runApp(MyApp());
  
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.green),
      home: PinPutView(),
    );
  }
}
  
class PinPutView extends StatefulWidget {
  @override
  PinPutViewState createState() => PinPutViewState();
}
  
class PinPutViewState extends State<PinPutView> {
  final _pinPutController = TextEditingController();
  final _pinPutController2 = TextEditingController();
  
  @override
  void initState() {
    super.initState();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("GeeksForGeeks"),
          centerTitle: true,
        ),
        body: Container(
          height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Expanded(child: darkRoundedPinPut()),
                Expanded(child: animatedBorders())
              ]),
        ));
  }
  
  Widget darkRoundedPinPut() {
    return PinPut(
      eachFieldWidth: 50.0,
      eachFieldHeight: 50.0,
      withCursor: true,
      fieldsCount: 5,
      controller: _pinPutController,
      eachFieldMargin: EdgeInsets.symmetric(horizontal: 10),
      onSubmit: (String pin) => _showSnackBar(pin),
      submittedFieldDecoration: BoxDecoration(
        color: Colors.green[800],
        borderRadius: BorderRadius.circular(15.0),
      ),
      selectedFieldDecoration: BoxDecoration(
        color: Colors.green[800],
        borderRadius: BorderRadius.circular(15.0),
      ),
      followingFieldDecoration: BoxDecoration(
        color: Colors.green[800],
        borderRadius: BorderRadius.circular(15.0),
      ),
      pinAnimationType: PinAnimationType.rotation,
      textStyle: TextStyle(color: Colors.white,
                           fontSize: 20.0,
                           height: 1),
    );
  }
  
  Widget animatedBorders() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: PinPut(
        fieldsCount: 4,
        eachFieldHeight: 50.0,
        withCursor: true,
        onSubmit: (String pin) => _showSnackBar(pin),
        controller: _pinPutController2,
        submittedFieldDecoration: BoxDecoration(
          border: Border.all(color: Colors.black),
          borderRadius: BorderRadius.circular(15.0),
        ).copyWith(
          borderRadius: BorderRadius.circular(20.0),
        ),
        selectedFieldDecoration: BoxDecoration(
          color: Colors.green,
          border: Border.all(color: Colors.black),
          borderRadius: BorderRadius.circular(15.0),
        ),
        followingFieldDecoration: BoxDecoration(
          border: Border.all(color: Colors.black),
          borderRadius: BorderRadius.circular(15.0),
        ).copyWith(
          borderRadius: BorderRadius.circular(5.0),
          border: Border.all(
            color: Colors.black,
          ),
        ),
      ),
    );
  }
  
  void _showSnackBar(String pin) {
    final snackBar = SnackBar(
      duration: Duration(seconds: 4),
      content: Container(
        height: 80.0,
        child: Center(
          child: Text(
            'Pin Submitted: $pin',
            style: TextStyle(fontSize: 25.0),
          ),
        ),
      ),
      backgroundColor: Colors.green,
    );
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(snackBar);
  }
}

Output:


Article Tags :