Flutter Mobile OTP Verification using Firebase with Getx Package Flutter by Rajesh Kumar Sahanee - October 19, 2022October 25, 20220 Post Views: 2,276 Hello Friends, Today I am going to share how to do Mobile OTP Verification in Flutter using Firebase with Getx Package. GetX is an extra-light and powerful solution for Flutter. It combines high-performance state management, intelligent dependency injection, and route management quickly and practically. We will follow following steps to achieve Mobile OTP Verification:- 1. Create Firebase Project 2. Create App in Firebase Project 3. Enable Mobile Authentication 4. Create Flutter App 5. Generate Certificate Fingerprint and Provide it Firebase Let’s get started 1. Creating Firebase Project Step-Wise screenshots Taken While Creating Project 2. Create App in Firebase Project Step-Wise screenshots Taken While Creating App in Project 3. Enable Mobile Authentication Step-Wise screenshots Taken While Enabling Mobile Authentication 4. Create Flutter App Paste google-services.json in App level directory which we have downloaded while creating App (see third screenshot of Create App in Firebase Project) Now let’s start coding build.gradle (Project Level) build.gradle buildscript { ext.kotlin_version = '1.6.10' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.gms:google-services:4.3.13" } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } 123456789101112131415161718192021222324252627282930313233 buildscript { ext.kotlin_version = '1.6.10' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.gms:google-services:4.3.13" }} allprojects { repositories { google() mavenCentral() }} rootProject.buildDir = '../build'subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}"}subprojects { project.evaluationDependsOn(':app')} task clean(type: Delete) { delete rootProject.buildDir} build.gradle (Module Level) build.gradle def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'com.google.gms.google-services' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.zatackcoder.otp_login_app" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 19 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation platform('com.google.firebase:firebase-bom:30.4.1') implementation 'com.google.firebase:firebase-analytics' implementation 'com.google.firebase:firebase-auth' } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576 def localProperties = new Properties()def localPropertiesFile = rootProject.file('local.properties')if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) }} def flutterRoot = localProperties.getProperty('flutter.sdk')if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")} def flutterVersionCode = localProperties.getProperty('flutter.versionCode')if (flutterVersionCode == null) { flutterVersionCode = '1'} def flutterVersionName = localProperties.getProperty('flutter.versionName')if (flutterVersionName == null) { flutterVersionName = '1.0'} apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'com.google.gms.google-services'apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion flutter.compileSdkVersion ndkVersion flutter.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.zatackcoder.otp_login_app" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 19 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } }} flutter { source '../..'} dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation platform('com.google.firebase:firebase-bom:30.4.1') implementation 'com.google.firebase:firebase-analytics' implementation 'com.google.firebase:firebase-auth'} pubspec.yaml pubspec.yaml name: otp_login_app description: A new Flutter project. # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # In Android, build-name is used as versionName while build-number used as versionCode. # Read more about Android versioning at https://developer.android.com/studio/publish/versioning # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: sdk: '>=2.18.2 <3.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions # consider running `flutter pub upgrade --major-versions`. Alternatively, # dependencies can be manually updated by changing the version numbers below to # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 #architecture get: ^4.6.5 #auth firebase_core: ^1.24.0 firebase_auth: ^3.11.0 dev_dependencies: flutter_test: sdk: flutter # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^2.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798 name: otp_login_appdescription: A new Flutter project. # The following line prevents the package from being accidentally published to# pub.dev using `flutter pub publish`. This is preferred for private packages.publish_to: 'none' # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application.# A version number is three numbers separated by dots, like 1.2.43# followed by an optional build number separated by a +.# Both the version and the builder number may be overridden in flutter# build by specifying --build-name and --build-number, respectively.# In Android, build-name is used as versionName while build-number used as versionCode.# Read more about Android versioning at https://developer.android.com/studio/publish/versioning# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.# Read more about iOS versioning at# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html# In Windows, build-name is used as the major, minor, and patch parts# of the product and file versions while build-number is used as the build suffix.version: 1.0.0+1 environment: sdk: '>=2.18.2 <3.0.0' # Dependencies specify other packages that your package needs in order to work.# To automatically upgrade your package dependencies to the latest versions# consider running `flutter pub upgrade --major-versions`. Alternatively,# dependencies can be manually updated by changing the version numbers below to# the latest version available on pub.dev. To see which dependencies have newer# versions available, run `flutter pub outdated`.dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 #architecture get: ^4.6.5 #auth firebase_core: ^1.24.0 firebase_auth: ^3.11.0 dev_dependencies: flutter_test: sdk: flutter # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is # activated in the `analysis_options.yaml` file located at the root of your # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^2.0.0 # For information on the generic Dart part of this file, see the# following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages.flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages main.dart main.dart import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:otp_login_app/views/home.dart'; import 'package:otp_login_app/views/login.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return GetMaterialApp( title: 'OTP Login App', theme: ThemeData( primarySwatch: Colors.blue, appBarTheme: const AppBarTheme( backgroundColor: Colors.white, iconTheme: IconThemeData(color: Colors.black))), getPages: [ GetPage(name: "/login", page: () => LoginPage()), GetPage(name: "/home", page: () => HomePage()), ], initialRoute: "/login", ); } } 12345678910111213141516171819202122232425262728293031323334 import 'package:firebase_core/firebase_core.dart';import 'package:flutter/material.dart';import 'package:get/get.dart';import 'package:otp_login_app/views/home.dart';import 'package:otp_login_app/views/login.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); runApp(const MyApp());} class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return GetMaterialApp( title: 'OTP Login App', theme: ThemeData( primarySwatch: Colors.blue, appBarTheme: const AppBarTheme( backgroundColor: Colors.white, iconTheme: IconThemeData(color: Colors.black))), getPages: [ GetPage(name: "/login", page: () => LoginPage()), GetPage(name: "/home", page: () => HomePage()), ], initialRoute: "/login", ); }} views/login.dart login.dart import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:otp_login_app/controllers/auth_controller.dart'; class LoginPage extends StatelessWidget { final authController = Get.put(AuthController()); final _formKey = GlobalKey<FormState>(); @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, backgroundColor: Colors.white, body: Stack(children: [ Obx(() => authController.isOtpSent.value ? _buildVerifyOtpForm() : _buildGetOtpForm()) ]), ); } Widget _buildGetOtpForm() { return SafeArea( child: Form( key: _formKey, child: Padding( padding: EdgeInsets.symmetric(vertical: 24, horizontal: 32), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Let's Sign in", style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), ), SizedBox( height: 10, ), Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Obx(() => Column( children: [ TextFormField( keyboardType: TextInputType.number, maxLength: 10, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), onChanged: (val) { authController.phoneNo.value = val; authController.showPrefix.value = val.length > 0; }, onSaved: (val) => authController.phoneNo.value = val!, validator: (val) => (val!.isEmpty || val!.length < 10) ? "Enter valid number" : null, decoration: InputDecoration( hintText: "Mobile Number", labelText: "Mobile Number", floatingLabelBehavior: FloatingLabelBehavior.auto, enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.black12), borderRadius: BorderRadius.circular(10)), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.black12), borderRadius: BorderRadius.circular(10)), prefix: authController.showPrefix.value ? Padding( padding: EdgeInsets.symmetric(horizontal: 8), child: Text( '(+91)', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ) : null, suffixIcon: _buildSuffixIcon(), ), ), SizedBox( height: 22, ), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { final form = _formKey.currentState; if (form!.validate()) { form.save(); authController.getOtp(); } }, style: ElevatedButton.styleFrom( foregroundColor: Colors.white, // backgroundColor: kPrimaryColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24.0), ), ), child: Padding( padding: EdgeInsets.all(14.0), child: Text( 'Get OTP', style: TextStyle(fontSize: 16), ), ), ), ) ], )), ), ], ), ), ), ); } Widget _buildVerifyOtpForm() { List<TextEditingController> otpFieldsControler = [ TextEditingController(), TextEditingController(), TextEditingController(), TextEditingController(), TextEditingController(), TextEditingController() ]; return SafeArea( child: Padding( padding: EdgeInsets.symmetric(vertical: 24, horizontal: 0), child: Column( children: [ Align( alignment: Alignment.topLeft, child: GestureDetector( onTap: () { authController.isOtpSent.value = false; Get.back(); }, child: Icon( Icons.arrow_back, size: 32, color: Colors.black54, ), ), ), SizedBox( height: 180, ), Text( 'Verification', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, ), ), SizedBox( height: 10, ), Text( "Enter your OTP code number", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black38, ), textAlign: TextAlign.center, ), SizedBox( height: 28, ), Container( padding: EdgeInsets.all(28), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _textFieldOTP( first: true, last: false, controller: otpFieldsControler[0]), _textFieldOTP( first: false, last: false, controller: otpFieldsControler[1]), _textFieldOTP( first: false, last: false, controller: otpFieldsControler[2]), _textFieldOTP( first: false, last: false, controller: otpFieldsControler[3]), _textFieldOTP( first: false, last: false, controller: otpFieldsControler[4]), _textFieldOTP( first: false, last: true, controller: otpFieldsControler[5]), ], ), Text( authController.statusMessage.value, style: TextStyle( color: authController.statusMessageColor.value, fontWeight: FontWeight.bold), ), SizedBox( height: 22, ), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { authController.otp.value = ""; otpFieldsControler.forEach((controller) { authController.otp.value += controller.text; }); authController.verifyOTP(); }, style: ButtonStyle( foregroundColor: MaterialStateProperty.all<Color>(Colors.white), // backgroundColor: // MaterialStateProperty.all<Color>(kPrimaryColor), shape: MaterialStateProperty.all<RoundedRectangleBorder>( RoundedRectangleBorder( borderRadius: BorderRadius.circular(24.0), ), ), ), child: Padding( padding: EdgeInsets.all(14.0), child: Text( 'Verify', style: TextStyle(fontSize: 16), ), ), ), ) ], ), ), SizedBox( height: 18, ), Text( "Didn't receive any code?", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black38, ), textAlign: TextAlign.center, ), SizedBox( height: 18, ), Obx( () => TextButton( onPressed: () => authController.resendOTP.value ? authController.resendOtp() : null, child: Text( authController.resendOTP.value ? "Resend New Code" : "Wait ${authController.resendAfter} seconds", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.purple, ), textAlign: TextAlign.center, ), ), ), ], ), ), ); } Widget _buildSuffixIcon() { return AnimatedOpacity( opacity: authController.phoneNo?.value.length == 10 ? 1.0 : 0.0, duration: const Duration(milliseconds: 250), child: Icon(Icons.check_circle, color: Colors.green, size: 32)); } Widget _textFieldOTP({bool first = true, last, controller}) { var height = (Get.width - 82) / 6; return Container( height: height, child: AspectRatio( aspectRatio: 1, child: TextField( autofocus: true, controller: controller, onChanged: (value) { if (value.length == 1 && last == false) { Get.focusScope?.nextFocus(); } if (value.length == 0 && first == false) { Get.focusScope?.previousFocus(); } }, showCursor: false, readOnly: false, textAlign: TextAlign.center, style: TextStyle(fontSize: height / 2, fontWeight: FontWeight.bold), keyboardType: TextInputType.number, maxLength: 1, decoration: InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), counter: Offstage(), enabledBorder: OutlineInputBorder( borderSide: BorderSide(width: 2, color: Colors.black12), borderRadius: BorderRadius.circular(12)), focusedBorder: OutlineInputBorder( borderSide: BorderSide(width: 2, color: Colors.purple), borderRadius: BorderRadius.circular(12)), ), ), ), ); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 import 'package:flutter/gestures.dart';import 'package:flutter/material.dart';import 'package:get/get.dart';import 'package:otp_login_app/controllers/auth_controller.dart'; class LoginPage extends StatelessWidget { final authController = Get.put(AuthController()); final _formKey = GlobalKey<FormState>(); @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, backgroundColor: Colors.white, body: Stack(children: [ Obx(() => authController.isOtpSent.value ? _buildVerifyOtpForm() : _buildGetOtpForm()) ]), ); } Widget _buildGetOtpForm() { return SafeArea( child: Form( key: _formKey, child: Padding( padding: EdgeInsets.symmetric(vertical: 24, horizontal: 32), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Let's Sign in", style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), ), SizedBox( height: 10, ), Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Obx(() => Column( children: [ TextFormField( keyboardType: TextInputType.number, maxLength: 10, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), onChanged: (val) { authController.phoneNo.value = val; authController.showPrefix.value = val.length > 0; }, onSaved: (val) => authController.phoneNo.value = val!, validator: (val) => (val!.isEmpty || val!.length < 10) ? "Enter valid number" : null, decoration: InputDecoration( hintText: "Mobile Number", labelText: "Mobile Number", floatingLabelBehavior: FloatingLabelBehavior.auto, enabledBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.black12), borderRadius: BorderRadius.circular(10)), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.black12), borderRadius: BorderRadius.circular(10)), prefix: authController.showPrefix.value ? Padding( padding: EdgeInsets.symmetric(horizontal: 8), child: Text( '(+91)', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ) : null, suffixIcon: _buildSuffixIcon(), ), ), SizedBox( height: 22, ), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { final form = _formKey.currentState; if (form!.validate()) { form.save(); authController.getOtp(); } }, style: ElevatedButton.styleFrom( foregroundColor: Colors.white, // backgroundColor: kPrimaryColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24.0), ), ), child: Padding( padding: EdgeInsets.all(14.0), child: Text( 'Get OTP', style: TextStyle(fontSize: 16), ), ), ), ) ], )), ), ], ), ), ), ); } Widget _buildVerifyOtpForm() { List<TextEditingController> otpFieldsControler = [ TextEditingController(), TextEditingController(), TextEditingController(), TextEditingController(), TextEditingController(), TextEditingController() ]; return SafeArea( child: Padding( padding: EdgeInsets.symmetric(vertical: 24, horizontal: 0), child: Column( children: [ Align( alignment: Alignment.topLeft, child: GestureDetector( onTap: () { authController.isOtpSent.value = false; Get.back(); }, child: Icon( Icons.arrow_back, size: 32, color: Colors.black54, ), ), ), SizedBox( height: 180, ), Text( 'Verification', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, ), ), SizedBox( height: 10, ), Text( "Enter your OTP code number", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black38, ), textAlign: TextAlign.center, ), SizedBox( height: 28, ), Container( padding: EdgeInsets.all(28), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _textFieldOTP( first: true, last: false, controller: otpFieldsControler[0]), _textFieldOTP( first: false, last: false, controller: otpFieldsControler[1]), _textFieldOTP( first: false, last: false, controller: otpFieldsControler[2]), _textFieldOTP( first: false, last: false, controller: otpFieldsControler[3]), _textFieldOTP( first: false, last: false, controller: otpFieldsControler[4]), _textFieldOTP( first: false, last: true, controller: otpFieldsControler[5]), ], ), Text( authController.statusMessage.value, style: TextStyle( color: authController.statusMessageColor.value, fontWeight: FontWeight.bold), ), SizedBox( height: 22, ), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { authController.otp.value = ""; otpFieldsControler.forEach((controller) { authController.otp.value += controller.text; }); authController.verifyOTP(); }, style: ButtonStyle( foregroundColor: MaterialStateProperty.all<Color>(Colors.white), // backgroundColor: // MaterialStateProperty.all<Color>(kPrimaryColor), shape: MaterialStateProperty.all<RoundedRectangleBorder>( RoundedRectangleBorder( borderRadius: BorderRadius.circular(24.0), ), ), ), child: Padding( padding: EdgeInsets.all(14.0), child: Text( 'Verify', style: TextStyle(fontSize: 16), ), ), ), ) ], ), ), SizedBox( height: 18, ), Text( "Didn't receive any code?", style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black38, ), textAlign: TextAlign.center, ), SizedBox( height: 18, ), Obx( () => TextButton( onPressed: () => authController.resendOTP.value ? authController.resendOtp() : null, child: Text( authController.resendOTP.value ? "Resend New Code" : "Wait ${authController.resendAfter} seconds", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.purple, ), textAlign: TextAlign.center, ), ), ), ], ), ), ); } Widget _buildSuffixIcon() { return AnimatedOpacity( opacity: authController.phoneNo?.value.length == 10 ? 1.0 : 0.0, duration: const Duration(milliseconds: 250), child: Icon(Icons.check_circle, color: Colors.green, size: 32)); } Widget _textFieldOTP({bool first = true, last, controller}) { var height = (Get.width - 82) / 6; return Container( height: height, child: AspectRatio( aspectRatio: 1, child: TextField( autofocus: true, controller: controller, onChanged: (value) { if (value.length == 1 && last == false) { Get.focusScope?.nextFocus(); } if (value.length == 0 && first == false) { Get.focusScope?.previousFocus(); } }, showCursor: false, readOnly: false, textAlign: TextAlign.center, style: TextStyle(fontSize: height / 2, fontWeight: FontWeight.bold), keyboardType: TextInputType.number, maxLength: 1, decoration: InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), counter: Offstage(), enabledBorder: OutlineInputBorder( borderSide: BorderSide(width: 2, color: Colors.black12), borderRadius: BorderRadius.circular(12)), focusedBorder: OutlineInputBorder( borderSide: BorderSide(width: 2, color: Colors.purple), borderRadius: BorderRadius.circular(12)), ), ), ), ); }} views/home.dart home.dart import 'package:flutter/material.dart'; import 'package:flutter/src/widgets/framework.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Home", style: TextStyle(color: Colors.black)), ), body: Center( child: Text("Home Page after Otp Verification"), ), ); } } 123456789101112131415161718 import 'package:flutter/material.dart';import 'package:flutter/src/widgets/framework.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Home", style: TextStyle(color: Colors.black)), ), body: Center( child: Text("Home Page after Otp Verification"), ), ); }} controllers/auth_controller.dart auth_controller.dart import 'dart:async'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:otp_login_app/views/home.dart'; class AuthController extends GetxController with GetSingleTickerProviderStateMixin { var showPrefix = false.obs; var isLogin = true; var phoneNo = "".obs; var otp = "".obs; var isOtpSent = false.obs; var resendAfter = 30.obs; var resendOTP = false.obs; var firebaseVerificationId = ""; var statusMessage = "".obs; var statusMessageColor = Colors.black.obs; var timer; AuthController() {} @override onInit() async { super.onInit(); } getOtp() async { FirebaseAuth.instance.verifyPhoneNumber( phoneNumber: '+91' + phoneNo.value, verificationCompleted: (PhoneAuthCredential credential) {}, verificationFailed: (FirebaseAuthException e) {}, codeSent: (String verificationId, int? resendToken) { firebaseVerificationId = verificationId; isOtpSent.value = true; statusMessage.value = "OTP sent to +91" + phoneNo.value; startResendOtpTimer(); }, codeAutoRetrievalTimeout: (String verificationId) {}, ); } resendOtp() async { resendOTP.value = false; FirebaseAuth.instance.verifyPhoneNumber( phoneNumber: '+91' + phoneNo.value, verificationCompleted: (PhoneAuthCredential credential) {}, verificationFailed: (FirebaseAuthException e) {}, codeSent: (String verificationId, int? resendToken) { firebaseVerificationId = verificationId; isOtpSent.value = true; statusMessage.value = "OTP re-sent to +91" + phoneNo.value; startResendOtpTimer(); }, codeAutoRetrievalTimeout: (String verificationId) {}, ); } verifyOTP() async { FirebaseAuth auth = FirebaseAuth.instance; try { statusMessage.value = "Verifying... " + otp.value; // Create a PhoneAuthCredential with the code PhoneAuthCredential credential = PhoneAuthProvider.credential( verificationId: firebaseVerificationId, smsCode: otp.value); // Sign the user in (or link) with the credential await auth.signInWithCredential(credential); Get.off(HomePage()); } catch (e) { statusMessage.value = "Invalid OTP"; statusMessageColor = Colors.red.obs; } } startResendOtpTimer() { timer = Timer.periodic(Duration(seconds: 1), (timer) { if (resendAfter.value != 0) { resendAfter.value--; } else { resendAfter.value = 30; resendOTP.value = true; timer.cancel(); } update(); }); } @override void dispose() { super.dispose(); } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394 import 'dart:async'; import 'package:firebase_auth/firebase_auth.dart';import 'package:flutter/material.dart';import 'package:get/get.dart';import 'package:otp_login_app/views/home.dart'; class AuthController extends GetxController with GetSingleTickerProviderStateMixin { var showPrefix = false.obs; var isLogin = true; var phoneNo = "".obs; var otp = "".obs; var isOtpSent = false.obs; var resendAfter = 30.obs; var resendOTP = false.obs; var firebaseVerificationId = ""; var statusMessage = "".obs; var statusMessageColor = Colors.black.obs; var timer; AuthController() {} @override onInit() async { super.onInit(); } getOtp() async { FirebaseAuth.instance.verifyPhoneNumber( phoneNumber: '+91' + phoneNo.value, verificationCompleted: (PhoneAuthCredential credential) {}, verificationFailed: (FirebaseAuthException e) {}, codeSent: (String verificationId, int? resendToken) { firebaseVerificationId = verificationId; isOtpSent.value = true; statusMessage.value = "OTP sent to +91" + phoneNo.value; startResendOtpTimer(); }, codeAutoRetrievalTimeout: (String verificationId) {}, ); } resendOtp() async { resendOTP.value = false; FirebaseAuth.instance.verifyPhoneNumber( phoneNumber: '+91' + phoneNo.value, verificationCompleted: (PhoneAuthCredential credential) {}, verificationFailed: (FirebaseAuthException e) {}, codeSent: (String verificationId, int? resendToken) { firebaseVerificationId = verificationId; isOtpSent.value = true; statusMessage.value = "OTP re-sent to +91" + phoneNo.value; startResendOtpTimer(); }, codeAutoRetrievalTimeout: (String verificationId) {}, ); } verifyOTP() async { FirebaseAuth auth = FirebaseAuth.instance; try { statusMessage.value = "Verifying... " + otp.value; // Create a PhoneAuthCredential with the code PhoneAuthCredential credential = PhoneAuthProvider.credential( verificationId: firebaseVerificationId, smsCode: otp.value); // Sign the user in (or link) with the credential await auth.signInWithCredential(credential); Get.off(HomePage()); } catch (e) { statusMessage.value = "Invalid OTP"; statusMessageColor = Colors.red.obs; } } startResendOtpTimer() { timer = Timer.periodic(Duration(seconds: 1), (timer) { if (resendAfter.value != 0) { resendAfter.value--; } else { resendAfter.value = 30; resendOTP.value = true; timer.cancel(); } update(); }); } @override void dispose() { super.dispose(); }} 5. Generate Certificate Fingerprint and Provide it to Firebase Run below command after going to android folder and copy SHA1 and SHA-256 and provide it to Firebase App by going to Project Settings see below screenshots for help Shell ./gradlew signingReport 1 ./gradlew signingReport Output Source Code https://github.com/rajeshkumarsahanee/otp_login_app Notes 1. Please make sure package name should match with your flutter app’s package name while creating App in Firebase Project Thanks for Stoping by If you find this helpful then please do share