Sunday, June 2, 2019

Flutter - Nexmo verify otp plugin.

Nowadays, all applications usually use a SMS text for authentication purpose, like OTP (One-Time-Passwords). Where a message(OTP) is sent by the service-provider. With the help of OTP, we can verify mobile numbers while e-commerce transactions, signing up and logging in the application. 
flutter-nexmo-verify-api-otp
To make this process effortless we can auto read the OTP as soon as it reaches the user's inbox. This flow helps the user to save a lot of apps switching from application to messenger app and then entering the authentication text to the app again. It is more widespread and safe because it is difficult to hack.

In this post, we are going to share a Nexmo verify API plugin that we have created by using Nexmo curl rest API for project simplicity and reduce development time for Nexmo Rest API.  We covering user verification by sending OTP to the mobile number.

Creating a new Project
1. Create a new project from File ⇒ New Flutter Project with your development IDE.
2. Now,  add the Nexmo Verify API plugin as a dependency in the pubspec.yaml file. Once you do that, you need to run flutter packages get.

3. After that open main.dart file and edit it. As we have set our theme and change debug banner property of Application.
main.dart
import 'package:flutter/material.dart'; import 'package:nexmo_verify_example/mobile_register_screen.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: new ThemeData( primaryColor: const Color(0xFF02BB9F), primaryColorDark: const Color(0xFF167F67), accentColor: const Color(0xFF02BB9F), ), home: MobileNumberVerifyScreen(), ); } }
main.dart will display mobile number input screen where we'll get OTP for mobile number verification.
4. As you can see below, we have a country code and mobile number input screen. Here, we'll enter this info and hit nexmo rest API to get OTP. After that, we'll move to OTP input screen.


here, we dart code snippet for the above screen.
mobile_register_screen.dart
import 'package:flutter/material.dart'; import 'package:nexmo_verify/basemodel.dart'; import 'package:nexmo_verify/model/nexmo_response.dart'; import 'package:nexmo_verify/nexmo_sms_verify.dart'; import 'package:nexmo_verify_example/otp_verification_screen.dart'; import 'package:nexmo_verify_example/progress_hud.dart'; class MobileNumberVerifyScreen extends StatefulWidget { MobileNumberVerifyScreen(); @override _LoginScreenState createState() => _LoginScreenState(); } class _LoginScreenState extends State<MobileNumberVerifyScreen> { bool _isLoading = false; final _teCountryCode = TextEditingController(); final _teMobileNumber = TextEditingController(); FocusNode _focusNodeFirstName = FocusNode(); NexmoSmsVerificationUtil _nexmoSmsVerificationUtil; @override void dispose() { _teCountryCode.dispose(); _teMobileNumber.dispose(); super.dispose(); } @override void initState() { super.initState(); _nexmoSmsVerificationUtil = NexmoSmsVerificationUtil(); _nexmoSmsVerificationUtil.initNexmo("api_key", "secret_key"); _teCountryCode.text = "91"; } void _submit() { if (_teCountryCode.text.isNotEmpty && _teMobileNumber.text.isNotEmpty) { showLoader(); _nexmoSmsVerificationUtil .sendOtp(_teCountryCode.text + _teMobileNumber.text, "Flutter") .then((dynamic res) { closeLoader(); nexmoSuccess((res as BaseModel).nexmoResponse); }); } } @override Widget build(BuildContext context) { var loginForm = Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ Container( alignment: FractionalOffset.center, margin: EdgeInsets.fromLTRB(10.0, 150.0, 10.0, 0.0), padding: EdgeInsets.fromLTRB(10.0, 50.0, 10.0, 100.0), decoration: BoxDecoration( color: Color.fromRGBO(255, 255, 255, 1.0), border: Border.all(color: Color(0x33A6A6A6)), borderRadius: BorderRadius.all(Radius.circular(6.0)), ), child: Column( children: <Widget>[ TextField( decoration: InputDecoration(hintText: "Country"), controller: _teCountryCode, ), TextField( decoration: InputDecoration(hintText: "Mobile Number"), controller: _teMobileNumber, focusNode: _focusNodeFirstName, keyboardType: TextInputType.number, ), ], ), ), RaisedButton( color: Color(0xFFFFA600), onPressed: _submit, shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(40))), child: Text( "OTP", style: TextStyle( fontSize: 18, color: Color(0xFFFFFFFF), ), ), ), ], ); var screenRoot = Container( height: double.infinity, child: SingleChildScrollView( child: Center( child: loginForm, ), ), ); return Scaffold( backgroundColor: Color(0xFFF1F1EF), body: ProgressHUD( child: screenRoot, inAsyncCall: _isLoading, ), ); } @override void onLoginError(String errorTxt) { setState(() => _isLoading = false); } @override void closeLoader() { setState(() => _isLoading = false); } @override void showLoader() { setState(() => _isLoading = true); } @override String getMobile() { return _teMobileNumber.text.toString(); } @override void nexmoSuccess(NexmoResponse nexmoResponse) { Navigator.pushReplacement( context, MaterialPageRoute( builder: (BuildContext context) => OtpVerificationScreenState( _teCountryCode.text + _teMobileNumber.text))); } }


5. Once we get success of sent OTP for entered mobile number. We'll move to OTP verify screen. Here, we have implemented a timer to wait of OTP and resend button to get  OTP again if it does not come within 5 minutes.


here, we have a code snippet for the above screen.
otp_verification_screen.dart
import 'package:flutter/material.dart'; import 'package:nexmo_verify/nexmo_sms_verify.dart'; import 'package:nexmo_verify_example/countdown_base.dart'; import 'package:nexmo_verify_example/custom_text_field.dart'; import 'package:nexmo_verify_example/progress_hud.dart'; class OtpVerificationScreenState extends StatefulWidget { String mobileNumber; OtpVerificationScreenState(this.mobileNumber); @override _OtpVerificationScreenState createState() => _OtpVerificationScreenState(mobileNumber); } class _OtpVerificationScreenState extends State<OtpVerificationScreenState> { bool _isLoading = false; bool _isResendEnable = false; final _formKey = GlobalKey<FormState>(); final _scaffoldKey = GlobalKey<ScaffoldState>(); String otpWaitTimeLabel = ""; bool _isMobileNumberEnter = false; String mobileNumber; final _teOtpDigitOne = TextEditingController(); final _teOtpDigitTwo = TextEditingController(); final _teOtpDigitThree = TextEditingController(); final _teOtpDigitFour = TextEditingController(); FocusNode _focusNodeDigitOne = FocusNode(); FocusNode _focusNodeDigitTwo = FocusNode(); FocusNode _focusNodeDigitThree = FocusNode(); FocusNode _focusNodeDigitFour = FocusNode(); NexmoSmsVerificationUtil _nexmoSmsVerificationUtil; _OtpVerificationScreenState(this.mobileNumber); @override void initState() { super.initState(); changeFocusListener(_teOtpDigitOne, _focusNodeDigitTwo); changeFocusListener(_teOtpDigitTwo, _focusNodeDigitThree); changeFocusListener(_teOtpDigitThree, _focusNodeDigitFour); startTimer(); _nexmoSmsVerificationUtil = NexmoSmsVerificationUtil(); _nexmoSmsVerificationUtil.initNexmo("api_key", "secret_key"); } bool isVerified = false; void _submit() { if (_isMobileNumberEnter) { showLoader(); _nexmoSmsVerificationUtil .verifyOtp(_teOtpDigitOne.text + _teOtpDigitTwo.text + _teOtpDigitThree.text + _teOtpDigitFour.text) .then((dynamic res) { closeLoader(); isVerified = true; }); } } @override Widget build(BuildContext context) { var otpBox = Padding( padding: EdgeInsets.only(left: 80.0, right: 80.0), child: Row( children: <Widget>[ inputBox(_teOtpDigitOne, _focusNodeDigitOne), SizedBox( width: 10.0, ), inputBox(_teOtpDigitTwo, _focusNodeDigitTwo), SizedBox( width: 10.0, ), inputBox(_teOtpDigitThree, _focusNodeDigitThree), SizedBox( width: 10.0, ), inputBox(_teOtpDigitFour, _focusNodeDigitFour), ], )); var form = Column( children: <Widget>[ Container( alignment: FractionalOffset.center, margin: EdgeInsets.fromLTRB(10.0, 150.0, 10.0, 0.0), decoration: BoxDecoration( color: Color(0xFFF9F9F9), borderRadius: BorderRadius.only( topLeft: Radius.circular(6.0), topRight: Radius.circular(6.0)), ), child: Column( children: <Widget>[ Container( alignment: FractionalOffset.center, padding: EdgeInsets.fromLTRB(10.0, 20.0, 10.0, 50.0), decoration: BoxDecoration( color: Color.fromRGBO(255, 255, 255, 1.0), border: Border.all(color: Color(0x33A6A6A6)), borderRadius: BorderRadius.only( topLeft: Radius.circular(6.0), topRight: Radius.circular(6.0)), ), child: Form( key: _formKey, child: Column( children: <Widget>[ SizedBox( width: 0.0, height: 30.0, ), Text( "OTP VERIFICATION", ), SizedBox( width: 0.0, height: 20.0, ), Text( "OTP", ), otpBox, SizedBox( width: 0.0, height: 20.0, ), Text( otpWaitTimeLabel, ), SizedBox( width: 0.0, height: 10.0, ), RaisedButton( color: Color(0xFFFFA600), onPressed: _submit, shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(40))), child: Text( "SUBMIT", style: TextStyle( fontSize: 18, color: Color(0xFFFFFFFF), ), ), ), ], ), ), ), ], ), ), Padding( padding: EdgeInsets.only(top: 20.0), child: RaisedButton( color: Color(0xFFFFA600), onPressed: _resendOtp, shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(40))), child: Text("RESEND OTP", style: TextStyle( fontSize: 18, color: Color(0xFFFFFFFF), )), ), ) ], ); return Scaffold( backgroundColor: Color(0xFFF1F1EF), key: _scaffoldKey, body: ProgressHUD( child: Container( child: SingleChildScrollView( child: Stack( children: <Widget>[ isVerified ? Container( height: 200.0, alignment: FractionalOffset.center, margin: EdgeInsets.fromLTRB(10.0, 150.0, 10.0, 0.0), decoration: BoxDecoration( color: Color(0xFFF9F9F9), borderRadius: BorderRadius.only( topLeft: Radius.circular(6.0), topRight: Radius.circular(6.0)), ), child: Text( "Verified: " + mobileNumber, style: TextStyle(fontSize: 16.0, color: Colors.blue), )) : form ], ), ), ), inAsyncCall: _isLoading, ), ); } @override void onLoginError(String errorTxt) { setState(() => _isLoading = false); } void changeFocusListener( TextEditingController teOtpDigitOne, FocusNode focusNodeDigitTwo) { teOtpDigitOne.addListener(() { if (teOtpDigitOne.text.length > 0 && focusNodeDigitTwo != null) { FocusScope.of(context).requestFocus(focusNodeDigitTwo); } setState(() {}); }); } @override void closeLoader() { setState(() => _isLoading = false); } @override void showLoader() { setState(() => _isLoading = true); } void _resendOtp() { if (_isResendEnable) { _nexmoSmsVerificationUtil.resentOtp(); } } void startTimer() { setState(() { _isResendEnable = false; }); var sub = CountDown(new Duration(minutes: 5)).stream.listen(null); sub.onData((Duration d) { setState(() { int sec = d.inSeconds % 60; otpWaitTimeLabel = d.inMinutes.toString() + ":" + sec.toString(); }); }); sub.onDone(() { setState(() { _isResendEnable = true; }); }); } @override void optSent() { startTimer(); } Widget inputBox( TextEditingController teOtpDigitOne, FocusNode focusNodeDigitOne) { return CustomTextField( inputBoxController: teOtpDigitOne, focusNod: focusNodeDigitOne, keyBoardType: TextInputType.number, textColor: 0xFFA6A6A6, textSize: 14.0, textFont: "Nexa_Bold", maxLength: 1, textAlign: TextAlign.center) .textField("", ""); } }
after successfully verify the mobile number. The above code snippet will display a success screen:




6. To create the above application, we have created some reuseable or util class, such as countdown_base.dart class will manage a timer for this example. We have used it in the OTP verification screen. As you can see above. 
countdown_base.dart
library countdown.base; import "dart:async"; class CountDown { /// reference point for start and resume DateTime _begin; Timer _timer; Duration _duration; Duration remainingTime; bool isPaused = false; StreamController<Duration> _controller; Duration _refresh; /// provide a way to send less data to the client but keep the data of the timer up to date int _everyTick, counter = 0; /// once you instantiate the CountDown you need to register to receive information CountDown(Duration duration, {Duration refresh: const Duration(milliseconds: 10), int everyTick: 1}) { _refresh = refresh; _everyTick = everyTick; this._duration = duration; _controller = new StreamController<Duration>(onListen: _onListen, onPause: _onPause, onResume: _onResume, onCancel: _onCancel); } Stream<Duration> get stream => _controller.stream; /// _onListen /// invoke when the first subscriber has subscribe and not before to avoid leak of memory _onListen() { // reference point _begin = new DateTime.now(); _timer = new Timer.periodic(_refresh, _tick); } /// the remaining time is set at '_refresh' ms accurate _onPause() { isPaused = true; _timer.cancel(); _timer = null; } /// ...restart the timer with the new duration _onResume() { _begin = new DateTime.now(); _duration = this.remainingTime; isPaused = false; // lance le timer _timer = new Timer.periodic(_refresh, _tick); } _onCancel() { // on pause we already cancel the _timer if (!isPaused) { _timer.cancel(); _timer = null; } // _controller.close(); // close automatically the "pipe" when the sub close it by sub.cancel() } void _tick(Timer timer) { counter++; Duration alreadyConsumed = new DateTime.now().difference(_begin); this.remainingTime = this._duration - alreadyConsumed; if (this.remainingTime.isNegative) { timer.cancel(); timer = null; // tell the onDone's subscriber that it's finish _controller.close(); } else { // here we can control the frequency of sending data if (counter % _everyTick == 0) { _controller.add(this.remainingTime); counter = 0; } } } }
here we have a customize textfield class custom_textfield.dart that we have used in the OTP verification screen. You can use it in your any Flutter project.
custom_textfield.dart
import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; class CustomTextField { final String outPrefixSvgPath; final double outPrefixSvgWidth; final double outPrefixSvgHeight; final int outPrefixSvgColor; EdgeInsets margin; final TextEditingController inputBoxController; final bool isPassword; final FocusNode focusNod; final TextInputType keyBoardType; final TextAlign textAlign; final Widget prefix; final Widget suffix; final int textColor; final String textFont; final double textSize; final bool clickable; final int maxLength; CustomTextField( {this.outPrefixSvgPath, this.outPrefixSvgWidth = 22.0, this.outPrefixSvgHeight = 22.0, this.outPrefixSvgColor, this.margin, this.inputBoxController, this.isPassword = false, this.focusNod, this.keyBoardType = TextInputType.text, this.prefix, this.suffix, this.textColor = 0xFF757575, this.textFont = "", this.textSize = 12.0, this.clickable = true, this.maxLength = 0, this.textAlign = TextAlign.left}); Widget textFieldWithOutPrefix(String hint, String errorMsg) { var loginBtn = new Container( margin: margin, child: new Row( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ new Padding( padding: EdgeInsets.only(right: 10.0), ), textField(hint, errorMsg), ], ), ); return loginBtn; } Widget textField(String hint, String errorMsg) { FocusNode focusNode = focusNod != null ? focusNod : new FocusNode(); var list = maxLength == 0 ? null : [ LengthLimitingTextInputFormatter(maxLength), ]; var loginBtn = new Expanded( child: new TextFormField( obscureText: isPassword, controller: inputBoxController, focusNode: focusNode, keyboardType: keyBoardType, enabled: clickable, textAlign: textAlign, inputFormatters: list, decoration: InputDecoration( labelText: hint, hintText: hint, prefixIcon: prefix, suffixIcon: suffix, ), validator: (val) => val.isEmpty ? errorMsg : null, onSaved: (val) => val, ), flex: 6, ); return loginBtn; } }
the progrees_hud.dart is a loader widget that displays when we'll hit any API.
progress_hud.dart
library modal_progress_hud; import 'package:flutter/material.dart'; class ProgressHUD extends StatelessWidget { final Widget child; final bool inAsyncCall; final double opacity; final Color color; final Animation<Color> valueColor; ProgressHUD({ Key key, @required this.child, @required this.inAsyncCall, this.opacity = 0.3, this.color = Colors.grey, this.valueColor, }) : super(key: key); @override Widget build(BuildContext context) { List<Widget> widgetList = new List<Widget>(); widgetList.add(child); if (inAsyncCall) { final modal = new Stack( children: [ new Opacity( opacity: opacity, child: ModalBarrier(dismissible: false, color: color), ), new Center( child: new CircularProgressIndicator( valueColor: valueColor, ), ), ], ); widgetList.add(modal); } return Stack( children: widgetList, ); } }


We hope the above example and plugin will help to create a mobile number verification feature in Flutter application. If you are facing any problem, please feel free to ask from comments.

Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...