Pin Code Fields In Flutter

Pin Code Fields In Flutter :

This flutter tutorial post is useful for OTP or pin code enter.

Features :

  • Automatically focuses the new field on typing and focuses previous field on deletation.
  • Field declaration
  • 3 different shapes for text fields. For example. box, underline, circle
  • Animated active, inactive field color switching
  • Autofocus option
  • iOS autofill support
PinCodeTextField(
                    length: 6,
                    obsecureText: false,
                    animationType: AnimationType.fade,
                    shape: PinCodeFieldShape.underline,
                    animationDuration: Duration(milliseconds: 300),
                    borderRadius: BorderRadius.circular(5),
                    fieldHeight: 50,
                    backgroundColor: Colors.white,
                    fieldWidth: 40,
                    currentText: (value) {
                      print(value);
                      setState(() {
                        currentText = value;
                      });
                    },
                  )

Screenshot :

Pin Code Fields In Flutter

pin_code_fields.dart

library pin_code_fields;

import 'dart:math';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:io' show Platform;

/// Pin code text fields which automatically changes focus and validates
class PinCodeTextField extends StatefulWidget {
  /// length of how many cells there should be. 3-8 is recommended by me
  final int length;

  /// you already know what it does i guess :P default is false
  final bool obsecureText;

  /// returns the current typed text in the fields
  final ValueChanged<String> currentText;

  /// this defines the shape of the input fields. Default is underlined
  final PinCodeFieldShape shape;

  /// the style of the text, default is [ fontSize: 20, color: Colors.black, fontWeight: FontWeight.bold]
  final TextStyle textStyle;

  /// background color for the whole row of pin code fields. Default is [Colors.white]
  final Color backgroundColor;

  /// Border radius of each pin code field
  final BorderRadius borderRadius;

  /// [height] for the pin code field. default is [50.0]
  final double fieldHeight;

  /// [width] for the pin code field. default is [40.0]
  final double fieldWidth;

  /// This defines how the elements in the pin code field align. Default to [MainAxisAlignment.spaceBetween]
  final MainAxisAlignment mainAxisAlignment;

  /// Colors of the input fields which have inputs. Default is [Colors.green]
  final Color activeColor;

  /// Colors of the input fields which don't have inputs. Default is [Colors.red]
  final Color inactiveColor;

  /// Border width for the each input fields. Default is [2.0]
  final double borderWidth;

  /// [AnimationType] for the text to appear in the pin code field. Default is [AnimationType.slide]
  final AnimationType animationType;

  /// Duration for the animation. Default is [Duration(milliseconds: 150)]
  final Duration animationDuration;

  /// [Curve] for the animation. Default is [Curves.easeInOut]
  final Curve animationCurve;

  /// [TextInputType] for the pin code fields. default is [TextInputType.visiblePassword]
  final TextInputType textInputType;

  /// If the pin code field should be autofocused or not. Default is [false]
  final bool autoFocus;

  PinCodeTextField(
      {Key key,
      @required this.length,
      this.obsecureText = false,
      @required this.currentText,
      this.backgroundColor = Colors.white,
      this.borderRadius,
      this.fieldHeight = 50,
      this.fieldWidth = 40,
      this.activeColor = Colors.red,
      this.inactiveColor = Colors.green,
      this.borderWidth = 2,
      this.mainAxisAlignment = MainAxisAlignment.spaceBetween,
      this.animationDuration = const Duration(milliseconds: 150),
      this.animationCurve = Curves.easeInOut,
      this.shape = PinCodeFieldShape.underline,
      this.animationType = AnimationType.slide,
      this.textInputType = TextInputType.visiblePassword,
      this.autoFocus = false,
      this.textStyle = const TextStyle(
          fontSize: 20, color: Colors.black, fontWeight: FontWeight.bold)})
      : super(key: key);

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

class _PinCodeTextFieldState extends State<PinCodeTextField> {
  TextEditingController _textEditingController;
  FocusNode _focusNode;
  List<String> _inputList;
  int _selectedIndex = 0;
  int _currentSize = 0;
  int _previousSize = 0;
  BorderRadius borderRadius;
  @override
  void initState() {
    _checkForInvalidValues();
    _textEditingController = TextEditingController();
    _textEditingController.addListener(() {
      var value = _textEditingController.value.text;
      if (value.length - _currentSize > 1) {
        setPastedText(value);
      } else if (value.length != _currentSize) {
        _previousSize = _currentSize;
        _currentSize = value.length;
        _selectedIndex = _currentSize;
        if (_currentSize > _previousSize) {
          setState(() {
            _inputList[_selectedIndex - 1] = value[value.length - 1];
          });
        } else if (_currentSize < _previousSize) {
          setState(() {
            _inputList[_previousSize - 1] = "";
          });
        }
        if (_currentSize == widget.length) {
          _focusNode.unfocus();
        }
        widget.currentText(value);
      }
    });
    if (widget.shape != PinCodeFieldShape.circle &&
        widget.shape != PinCodeFieldShape.underline) {
      borderRadius = widget.borderRadius;
    }
    _focusNode = FocusNode();
    _inputList = List<String>(widget.length);
    _initializeValues();
    super.initState();
  }

  void _checkForInvalidValues() {
    assert(widget.length != null && widget.length > 0);
    assert(widget.obsecureText != null);
    assert(widget.currentText != null);
    assert(widget.backgroundColor != null);
    assert(widget.fieldHeight != null && widget.fieldHeight > 0);
    assert(widget.fieldWidth != null && widget.fieldWidth > 0);
    assert(widget.activeColor != null);
    assert(widget.inactiveColor != null);
    assert(widget.borderWidth != null && widget.borderWidth >= 0);
    assert(widget.mainAxisAlignment != null);
    assert(widget.animationDuration != null);
    assert(widget.animationCurve != null);
    assert(widget.shape != null);
    assert(widget.animationType != null);
    assert(widget.textStyle != null);
    assert(widget.textInputType != null);
    assert(widget.autoFocus != null);
  }

  @override
  void dispose() {
    _textEditingController.dispose();
    _focusNode.dispose();
    super.dispose();
  }

  void _initializeValues() {
    for (int i = 0; i < _inputList.length; i++) {
      _inputList[i] = "";
    }
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.bottomCenter,
      children: <Widget>[
        Container(
          color: widget.backgroundColor.withOpacity(1),
          padding: const EdgeInsets.only(bottom: 4.0),
          child: AbsorbPointer(
            // this is a hidden textfield under the pin code fields. This is why we need a background color to hide it
            absorbing: true, // it prevents on tap on the text field
            child: TextField(
              controller: _textEditingController,
              focusNode: _focusNode,
              enabled: true,
              autofocus: widget.autoFocus,
              autocorrect: true,
              keyboardType: widget.textInputType,
              inputFormatters: [
                LengthLimitingTextInputFormatter(
                    widget.length), // this limits the input length
              ],
              enableInteractiveSelection: true,
              showCursor: true,
              decoration: InputDecoration(
                contentPadding: EdgeInsets.all(0),
              ),
            ),
          ),
        ),
        Container(
          color: widget.backgroundColor.withOpacity(1),
          constraints: BoxConstraints(minHeight: 30),
          padding: EdgeInsets.symmetric(vertical: 8),
          child: Row(
            mainAxisAlignment: widget.mainAxisAlignment,
            children: _generateFields(),
          ),
        ),
      ],
    );
  }

  List<Widget> _generateFields() {
    var result = <Widget>[];
    for (int i = 0; i < widget.length; i++) {
      result.add(GestureDetector(
        onTap: _onFocus,
        onLongPress: () async {
          var data = await Clipboard.getData('text/plain');
          if (data.text.isNotEmpty) {
            showDialog(
                context: context,
                builder: (context) {
                  return Platform.isAndroid
                      ? AlertDialog(
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(10)),
                          title: Text("Paste Code"),
                          content: RichText(
                            text: TextSpan(
                                text: "Do you want paste this code ",
                                style: TextStyle(
                                    color: Theme.of(context)
                                        .textTheme
                                        .button
                                        .color),
                                children: [
                                  TextSpan(
                                      text: data.text,
                                      style: TextStyle(
                                          fontWeight: FontWeight.bold,
                                          color: Colors.green.shade600)),
                                  TextSpan(
                                      text: " ?",
                                      style: TextStyle(
                                          color: Theme.of(context)
                                              .textTheme
                                              .button
                                              .color))
                                ]),
                          ),
                          actions: _getActionButtons(data.text),
                        )
                      : CupertinoAlertDialog(
                          title: Text("Paste Code"),
                          content: RichText(
                            text: TextSpan(
                                text: "Do you want paste this code ",
                                style: TextStyle(
                                    color: Theme.of(context)
                                        .textTheme
                                        .button
                                        .color),
                                children: [
                                  TextSpan(
                                      text: data.text,
                                      style: TextStyle(
                                          fontWeight: FontWeight.bold,
                                          color: Colors.green.shade600)),
                                  TextSpan(
                                      text: " ?",
                                      style: TextStyle(
                                          color: Theme.of(context)
                                              .textTheme
                                              .button
                                              .color))
                                ]),
                          ),
                          actions: _getActionButtons(data.text),
                        );
                });
          }
        },
        child: AnimatedContainer(
          curve: widget.animationCurve,
          duration: widget.animationDuration,
          width: widget.fieldWidth,
          height: widget.fieldHeight,
          decoration: BoxDecoration(
              shape: widget.shape == PinCodeFieldShape.circle
                  ? BoxShape.circle
                  : BoxShape.rectangle,
              borderRadius: borderRadius,
              border: widget.shape == PinCodeFieldShape.underline
                  ? Border(
                      bottom: BorderSide(
                      color: _selectedIndex <= i
                          ? widget.activeColor
                          : widget.inactiveColor,
                      width: widget.borderWidth,
                    ))
                  : Border.all(
                      color: _selectedIndex <= i
                          ? widget.activeColor
                          : widget.inactiveColor,
                      width: widget.borderWidth,
                    )),
          child: Center(
              child: AnimatedSwitcher(
            switchInCurve: widget.animationCurve,
            switchOutCurve: widget.animationCurve,
            duration: widget.animationDuration,
            transitionBuilder: (child, animation) {
              if (widget.animationType == AnimationType.scale) {
                return ScaleTransition(
                  scale: animation,
                  child: child,
                );
              } else if (widget.animationType == AnimationType.fade) {
                return FadeTransition(
                  opacity: animation,
                  child: child,
                );
              } else if (widget.animationType == AnimationType.none) {
                return child;
              } else {
                return SlideTransition(
                  position: Tween<Offset>(
                    begin: const Offset(0, .5),
                    end: Offset.zero,
                  ).animate(animation),
                  child: child,
                );
              }
            },
            child: Text(
              widget.obsecureText && _inputList[i].isNotEmpty
                  ? "‚óŹ"
                  : _inputList[i],
              key: ValueKey(_inputList[i]),
              style: widget.textStyle,
            ),
          )),
        ),
      ));
    }
    return result;
  }

  void _onFocus() {
    if (_focusNode.hasFocus) {
      _focusNode.unfocus();
    }

    FocusScope.of(context).requestFocus(_focusNode);
  }

  void setPastedText(String data) async {
    String resultedString="";
    for (int i = 0; i < min(data.length, widget.length); i++) {
      resultedString += data[i];
      if (i == 0) {
        setState(() {
          _inputList[i] = data[i];
          _previousSize = _currentSize;
          _currentSize = i;
          _selectedIndex = _currentSize;
        });
      } else {
        setState(() {
          _inputList[i] = data[i];
          _previousSize = _currentSize;
          _currentSize = i + 1;
          _selectedIndex = _currentSize;
        });
      }
    }
    setState(() {});
    widget.currentText(resultedString);
  }

  List<Widget> _getActionButtons(String data) {
    var resultList = <Widget>[];
    if (Platform.isAndroid) {
      resultList.addAll([
        FlatButton(
          child: Text("Cancel"),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
        FlatButton(
          child: Text("Paste"),
          onPressed: () {
            _textEditingController.text = data;
            // setPastedText(data);
            Navigator.pop(context);
          },
        ),
      ]);
    } else if (Platform.isIOS) {
      resultList.addAll([
        CupertinoDialogAction(
          child: Text("Cancel"),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
        CupertinoDialogAction(
          child: Text("Paste"),
          onPressed: () {
            _textEditingController.text = data;
            // setPastedText(data);
            Navigator.pop(context);
          },
        ),
      ]);
    }
    return resultList;
  }
}

enum AnimationType { scale, slide, fade, none }

enum PinCodeFieldShape { box, underline, circle }

main.dart

import 'package:flare_flutter/flare_actor.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'pin_code_fields.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Tutorial',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PinCodeVerificationScreen(
          "+91 0000000000"), // a random number, please don't call xD
    );
  }
}

class PinCodeVerificationScreen extends StatefulWidget {
  final String phoneNumber;
  PinCodeVerificationScreen(this.phoneNumber);
  @override
  _PinCodeVerificationScreenState createState() =>
      _PinCodeVerificationScreenState();
}

class _PinCodeVerificationScreenState extends State<PinCodeVerificationScreen> {
  var onTapRecognizer;

  bool hasError = false;
  String currentText = "";
  final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
  @override
  void initState() {
    onTapRecognizer = TapGestureRecognizer()
      ..onTap = () {
        Navigator.pop(context);
      };
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      key: scaffoldKey,
      body: GestureDetector(
        onTap: () {
          FocusScope.of(context).requestFocus(FocusNode());
        },
        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: const EdgeInsets.symmetric(vertical: 8.0),
                child: Text(
                  'Phone Number Verification',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
                  textAlign: TextAlign.center,
                ),
              ),
              Padding(
                padding:
                    const EdgeInsets.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,
              ),
              Padding(
                  padding:
                      const EdgeInsets.symmetric(vertical: 8.0, horizontal: 30),
                  child: PinCodeTextField(
                    length: 6,
                    obsecureText: false,
                    animationType: AnimationType.fade,
                    shape: PinCodeFieldShape.underline,
                    animationDuration: Duration(milliseconds: 300),
                    borderRadius: BorderRadius.circular(5),
                    fieldHeight: 50,
                    backgroundColor: Colors.white,
                    fieldWidth: 40,
                    currentText: (value) {
                      print(value);
                      setState(() {
                        currentText = value;
                      });
                    },
                  )),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 30.0),
                child: Text(
                  hasError ? "*Please fill up all the cells properly" : "",
                  style: TextStyle(color: Colors.red.shade300, fontSize: 15),
                ),
              ),
              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:
                    const EdgeInsets.symmetric(vertical: 16.0, horizontal: 30),
                child: ButtonTheme(
                  height: 50,
                  child: FlatButton(
                    onPressed: () {
                      // conditions for validating
                      if (currentText.length != 6 || currentText != "towtow") {
                        setState(() {
                          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)
                    ]),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

pubspec.yaml

name: flutter_tutorial
description: A new Flutter application.
version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  flare_flutter:

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

  assets:
    - assets/verify.png
    - assets/otp.flr

The flutter tutorial  is a website that bring you the latest and amazing resources of code. All the languages codes are included in this website. The languages like flutter, android, java,kotlin etc.with the help of this languages any user can develop the beautiful application

For more information about Flutter. visit www.fluttertutorial.in