Making Phone Verification Textfield UI in flutter
You can use PinCodeTextField https://pub.dev/packages/pin_code_fields
You can use different shape and animation also it handle the problem which is occurs when you remove the text in middle.
I have made my own UI, if you like it, use it, or you can customise to your own needs
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class Otp extends StatefulWidget {
final String email;
final String newEmail;
final bool isGuestCheckOut;
const Otp({
Key key,
@required this.email,
this.newEmail = "",
this.isGuestCheckOut,
}) : super(key: key);
@override
_OtpState createState() => new _OtpState();
}
class _OtpState extends State<Otp> with SingleTickerProviderStateMixin {
// Constants
final int time = 30;
AnimationController _controller;
// Variables
Size _screenSize;
int _currentDigit;
int _firstDigit;
int _secondDigit;
int _thirdDigit;
int _fourthDigit;
Timer timer;
int totalTimeInSeconds;
bool _hideResendButton;
String userName = "";
bool didReadNotifications = false;
int unReadNotificationsCount = 0;
// Returns "Appbar"
get _getAppbar {
return new AppBar(
backgroundColor: Colors.transparent,
elevation: 0.0,
leading: new InkWell(
borderRadius: BorderRadius.circular(30.0),
child: new Icon(
Icons.arrow_back,
color: Colors.black54,
),
onTap: () {
Navigator.pop(context);
},
),
centerTitle: true,
);
}
// Return "Verification Code" label
get _getVerificationCodeLabel {
return new Text(
"Verification Code",
textAlign: TextAlign.center,
style: new TextStyle(
fontSize: 28.0, color: Colors.black, fontWeight: FontWeight.bold),
);
}
// Return "Email" label
get _getEmailLabel {
return new Text(
"Please enter the OTP sent\non your registered Email ID.",
textAlign: TextAlign.center,
style: new TextStyle(
fontSize: 18.0, color: Colors.black, fontWeight: FontWeight.w600),
);
}
// Return "OTP" input field
get _getInputField {
return new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_otpTextField(_firstDigit),
_otpTextField(_secondDigit),
_otpTextField(_thirdDigit),
_otpTextField(_fourthDigit),
],
);
}
// Returns "OTP" input part
get _getInputPart {
return new Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
_getVerificationCodeLabel,
_getEmailLabel,
_getInputField,
_hideResendButton ? _getTimerText : _getResendButton,
_getOtpKeyboard
],
);
}
// Returns "Timer" label
get _getTimerText {
return Container(
height: 32,
child: new Offstage(
offstage: !_hideResendButton,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Icon(Icons.access_time),
new SizedBox(
width: 5.0,
),
OtpTimer(_controller, 15.0, Colors.black)
],
),
),
);
}
// Returns "Resend" button
get _getResendButton {
return new InkWell(
child: new Container(
height: 32,
width: 120,
decoration: BoxDecoration(
color: Colors.black,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(32)),
alignment: Alignment.center,
child: new Text(
"Resend OTP",
style:
new TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
),
),
onTap: () {
// Resend you OTP via API or anything
},
);
}
// Returns "Otp" keyboard
get _getOtpKeyboard {
return new Container(
height: _screenSize.width - 80,
child: new Column(
children: <Widget>[
new Expanded(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_otpKeyboardInputButton(
label: "1",
onPressed: () {
_setCurrentDigit(1);
}),
_otpKeyboardInputButton(
label: "2",
onPressed: () {
_setCurrentDigit(2);
}),
_otpKeyboardInputButton(
label: "3",
onPressed: () {
_setCurrentDigit(3);
}),
],
),
),
new Expanded(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_otpKeyboardInputButton(
label: "4",
onPressed: () {
_setCurrentDigit(4);
}),
_otpKeyboardInputButton(
label: "5",
onPressed: () {
_setCurrentDigit(5);
}),
_otpKeyboardInputButton(
label: "6",
onPressed: () {
_setCurrentDigit(6);
}),
],
),
),
new Expanded(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_otpKeyboardInputButton(
label: "7",
onPressed: () {
_setCurrentDigit(7);
}),
_otpKeyboardInputButton(
label: "8",
onPressed: () {
_setCurrentDigit(8);
}),
_otpKeyboardInputButton(
label: "9",
onPressed: () {
_setCurrentDigit(9);
}),
],
),
),
new Expanded(
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new SizedBox(
width: 80.0,
),
_otpKeyboardInputButton(
label: "0",
onPressed: () {
_setCurrentDigit(0);
}),
_otpKeyboardActionButton(
label: new Icon(
Icons.backspace,
color: Colors.black,
),
onPressed: () {
setState(() {
if (_fourthDigit != null) {
_fourthDigit = null;
} else if (_thirdDigit != null) {
_thirdDigit = null;
} else if (_secondDigit != null) {
_secondDigit = null;
} else if (_firstDigit != null) {
_firstDigit = null;
}
});
}),
],
),
),
],
));
}
// Overridden methods
@override
void initState() {
totalTimeInSeconds = time;
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: time))
..addStatusListener((status) {
if (status == AnimationStatus.dismissed) {
setState(() {
_hideResendButton = !_hideResendButton;
});
}
});
_controller.reverse(
from: _controller.value == 0.0 ? 1.0 : _controller.value);
_startCountdown();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
_screenSize = MediaQuery.of(context).size;
return new Scaffold(
appBar: _getAppbar,
backgroundColor: Colors.white,
body: new Container(
width: _screenSize.width,
// padding: new EdgeInsets.only(bottom: 16.0),
child: _getInputPart,
),
);
}
// Returns "Otp custom text field"
Widget _otpTextField(int digit) {
return new Container(
width: 35.0,
height: 45.0,
alignment: Alignment.center,
child: new Text(
digit != null ? digit.toString() : "",
style: new TextStyle(
fontSize: 30.0,
color: Colors.black,
),
),
decoration: BoxDecoration(
// color: Colors.grey.withOpacity(0.4),
border: Border(
bottom: BorderSide(
width: 2.0,
color: Colors.black,
))),
);
}
// Returns "Otp keyboard input Button"
Widget _otpKeyboardInputButton({String label, VoidCallback onPressed}) {
return new Material(
color: Colors.transparent,
child: new InkWell(
onTap: onPressed,
borderRadius: new BorderRadius.circular(40.0),
child: new Container(
height: 80.0,
width: 80.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
),
child: new Center(
child: new Text(
label,
style: new TextStyle(
fontSize: 30.0,
color: Colors.black,
),
),
),
),
),
);
}
// Returns "Otp keyboard action Button"
_otpKeyboardActionButton({Widget label, VoidCallback onPressed}) {
return new InkWell(
onTap: onPressed,
borderRadius: new BorderRadius.circular(40.0),
child: new Container(
height: 80.0,
width: 80.0,
decoration: new BoxDecoration(
shape: BoxShape.circle,
),
child: new Center(
child: label,
),
),
);
}
// Current digit
void _setCurrentDigit(int i) {
setState(() {
_currentDigit = i;
if (_firstDigit == null) {
_firstDigit = _currentDigit;
} else if (_secondDigit == null) {
_secondDigit = _currentDigit;
} else if (_thirdDigit == null) {
_thirdDigit = _currentDigit;
} else if (_fourthDigit == null) {
_fourthDigit = _currentDigit;
var otp = _firstDigit.toString() +
_secondDigit.toString() +
_thirdDigit.toString() +
_fourthDigit.toString();
// Verify your otp by here. API call
}
});
}
Future<Null> _startCountdown() async {
setState(() {
_hideResendButton = true;
totalTimeInSeconds = time;
});
_controller.reverse(
from: _controller.value == 0.0 ? 1.0 : _controller.value);
}
void clearOtp() {
_fourthDigit = null;
_thirdDigit = null;
_secondDigit = null;
_firstDigit = null;
setState(() {});
}
}
class OtpTimer extends StatelessWidget {
final AnimationController controller;
double fontSize;
Color timeColor = Colors.black;
OtpTimer(this.controller, this.fontSize, this.timeColor);
String get timerString {
Duration duration = controller.duration * controller.value;
if (duration.inHours > 0) {
return '${duration.inHours}:${duration.inMinutes % 60}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
return '${duration.inMinutes % 60}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
Duration get duration {
Duration duration = controller.duration;
return duration;
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (BuildContext context, Widget child) {
return new Text(
timerString,
style: new TextStyle(
fontSize: fontSize,
color: timeColor,
fontWeight: FontWeight.w600),
);
});
}
}