DEV Community

Cover image for How I use Amplify Auth with Flutter
Offline Programmer
Offline Programmer

Posted on • Edited on

How I use Amplify Auth with Flutter

One of my goals this year is to publish my App KidzTokenz on Apple App Store. For that, I am planning to use Flutter and AWSAmplify.

In this post, we will integrate the Auth category into a Flutter App and run it on iOS.

Amplify Setup

I am going to use Amplify Admin UI to create the backend. Use the instruction I shared in the previous post (link below) to create a project of the name (AuthFlutter).

Configure and deploy the Authentication. We are going to use (Email) for verification.

Screen Shot 2021-01-13 at 1.24.25 PM

Project Setup

Using Terminal, create a new flutter project by running the command below

 flutter create auth_for_flutter 
Enter fullscreen mode Exit fullscreen mode

Next, let's open the project folder using the VS Code. Make sure the integrated terminal is in the ios folder.

Screen Shot 2021-01-12 at 1.43.06 PM

Run the following commands, and you will get a Podfile in the ios folder.

 sudo gem install cocoapods pod init 
Enter fullscreen mode Exit fullscreen mode

Update the Podile as below

 platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end 
Enter fullscreen mode Exit fullscreen mode

In XCode, select your project in the Project Navigator and select the “Info” tab. Set the Deployment Target to at least 13.0.

Screen Shot 2021-01-12 at 2.13.21 PM

Open the App‘s pubspec.yaml and add the following dependencies below the line “sdk:flutter”.

 dependencies: flutter: sdk: flutter amplify_core: '<1.0.0' amplify_auth_cognito: '<1.0.0' 
Enter fullscreen mode Exit fullscreen mode

Run the command below in the in ios folder

 pod install 
Enter fullscreen mode Exit fullscreen mode

This is going to install the required Pods.

Screen Shot 2021-01-12 at 2.23.22 PM

Pull down the Amplify backend we created above by running the pull command from the Admin UI in the root folder of the App

Screen Shot 2021-01-13 at 4.20.06 PM

Answer the prompted questions, and once complete, you will get a confirmation as below.

 Added backend environment config object to your project. Run 'amplify pull' to sync upstream changes. 
Enter fullscreen mode Exit fullscreen mode

Update the app‘s pubspec.yaml to add the image file we are going to use

 # To add assets to your application, add an assets section, like this: assets: - assets/images/applogo.png 
Enter fullscreen mode Exit fullscreen mode

Run the App to make sure it builds successfully

Screen Shot 2021-01-12 at 2.25.57 PM

The Implementation

We are going to use an enum for the authentication's status. Create the file below in the (lib) folder

account_screens_enum.dart

 enum AccountStatus { sign_in, sign_up, reset_password, confirm_code, main_screen } 
Enter fullscreen mode Exit fullscreen mode

Next, let's create the widgets in (lib\widgets) folder.

sign_up.dart

To create an account.

 import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:auth_for_flutter/account_screens_enum.dart'; import 'package:auth_for_flutter/widgets/confirm_signup.dart'; import 'package:auth_for_flutter/widgets/error_view.dart'; import 'package:flutter/material.dart'; class SignUpView extends StatefulWidget { final Function _displayAccountWidget; const SignUpView(this._displayAccountWidget); @override _SignUpViewState createState() => _SignUpViewState(); } class _SignUpViewState extends State<SignUpView> { final emailController = TextEditingController(); final passwordController = TextEditingController(); bool _isSignedUp = false; DateTime signupDate; String _signUpError = ""; List<String> _signUpExceptions = []; @override void initState() { super.initState(); } void _setError(AuthError error) { setState(() { _signUpError = error.cause; _signUpExceptions.clear(); error.exceptionList.forEach((el) { _signUpExceptions.add(el.exception); }); }); } void _signUp(BuildContext context) async { try { Map<String, dynamic> userAttributes = { "email": emailController.text.trim(), "preferred_username": emailController.text.trim(), // additional attributes as needed }; SignUpResult res = await Amplify.Auth.signUp( username: emailController.text.trim(), password: passwordController.text.trim(), options: CognitoSignUpOptions(userAttributes: userAttributes)); print(res.isSignUpComplete); setState(() { _isSignedUp = true; }); } on AuthError catch (error) { _setError(error); } } @override Widget build(BuildContext context) { return Card( elevation: 2, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Expanded( child: Padding( padding: EdgeInsets.all(10.0), child: Column( children: [ Visibility( visible: !_isSignedUp, child: Column(children: [ TextFormField( enableSuggestions: false, decoration: const InputDecoration( icon: Icon(Icons.email), hintText: 'Email', labelText: 'Email *', ), controller: emailController, keyboardType: TextInputType.emailAddress, ), TextFormField( obscureText: true, enableSuggestions: false, autocorrect: false, decoration: const InputDecoration( icon: Icon(Icons.lock), hintText: 'Password', labelText: 'Password *', ), controller: passwordController, ), const Padding(padding: EdgeInsets.all(10.0)), FlatButton( textColor: Colors.black, // Theme.of(context).primaryColor, color: Colors.amber, onPressed: () => _signUp(context), child: Text( 'Create Account', style: TextStyle(fontWeight: FontWeight.bold), ), ), FlatButton( height: 5, onPressed: _displaySignIn, child: Text( 'Already registered? Sign In', style: Theme.of(context).textTheme.subtitle2, ), ), ]), ), Visibility( visible: _isSignedUp, child: Column(children: [ ConfirmSignup(emailController.text.trim(), _setError), ])), ErrorView(_signUpError, _signUpExceptions) ], ), ), ), ], ), ); } void _displaySignIn() { widget._displayAccountWidget(AccountStatus.sign_in.index); } } 
Enter fullscreen mode Exit fullscreen mode

confirm_signup.dart

To submit the signup confirmation code.

 import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:auth_for_flutter/screens/next_screen.dart'; import 'package:flutter/material.dart'; class ConfirmSignup extends StatelessWidget { final codeController = TextEditingController(); final String userName; final Function setError; ConfirmSignup(this.userName, this.setError); void _skip_confirm_signup(BuildContext context) { _go_to_NextScreen(context); } void _confirm_signup(BuildContext context) async { try { SignUpResult res = await Amplify.Auth.confirmSignUp( username: this.userName, confirmationCode: codeController.text.trim()); _go_to_NextScreen(context); } on AuthError catch (e) { setError(e); } } void _go_to_NextScreen(BuildContext context) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) { return NextScreen(); }, ), ); } @override Widget build(BuildContext context) { return Container( // decoration: BoxDecoration(borderRadius: BorderRadius.circular(20)), padding: EdgeInsets.all(5), child: Column( children: [ TextFormField( controller: codeController, decoration: const InputDecoration( icon: Icon(Icons.confirmation_number), hintText: 'The code we sent you', labelText: 'Confirmation Code *', )), FlatButton( textColor: Colors.black, // Theme.of(context).primaryColor, color: Colors.amber, onPressed: () => _confirm_signup(context), child: Text( 'Confirm Sign Up', style: TextStyle(fontWeight: FontWeight.bold), ), ), ], ), ); } } 
Enter fullscreen mode Exit fullscreen mode

reset_password.dart

To request a password reset operation.

 import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:auth_for_flutter/account_screens_enum.dart'; import 'package:auth_for_flutter/widgets/confirm_reset_password.dart'; import 'package:auth_for_flutter/widgets/error_view.dart'; import 'package:flutter/material.dart'; class ResetPasswordView extends StatefulWidget { final Function _displayAccountWidget; const ResetPasswordView(this._displayAccountWidget); @override _ResetPasswordViewState createState() => _ResetPasswordViewState(); } class _ResetPasswordViewState extends State<ResetPasswordView> { final emailController = TextEditingController(); bool _isPasswordReset = false; String _signUpError = ""; List<String> _signUpExceptions = []; @override void initState() { super.initState(); } void _setError(AuthError error) { setState(() { _signUpError = error.cause; _signUpExceptions.clear(); error.exceptionList.forEach((el) { _signUpExceptions.add(el.exception); }); }); } void _resetPassword(BuildContext context) async { try { ResetPasswordResult res = await Amplify.Auth.resetPassword( username: emailController.text.trim(), ); setState(() { _isPasswordReset = true; }); } on AuthError catch (e) { setState(() { _signUpError = e.cause; _signUpExceptions.clear(); e.exceptionList.forEach((el) { _signUpExceptions.add(el.exception); }); }); } } @override Widget build(BuildContext context) { return Card( elevation: 2, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Expanded( child: Padding( padding: EdgeInsets.all(10.0), child: Column( children: [ Visibility( visible: !_isPasswordReset, child: Column(children: [ TextFormField( enableSuggestions: false, decoration: const InputDecoration( icon: Icon(Icons.email), hintText: 'Email', labelText: 'Email *', ), controller: emailController, keyboardType: TextInputType.emailAddress, ), const Padding(padding: EdgeInsets.all(10.0)), FlatButton( textColor: Colors.black, // Theme.of(context).primaryColor, color: Colors.amber, onPressed: () => _resetPassword(context), child: Text( 'Reset Password', style: TextStyle(fontWeight: FontWeight.bold), ), ), FlatButton( height: 5, onPressed: _displayCreateAccount, child: Text( 'Create Account', style: Theme.of(context).textTheme.subtitle2, ), ), ]), ), Visibility( visible: _isPasswordReset, child: Column(children: [ ConfirmResetPassword( emailController.text.trim(), _setError), ])), ErrorView(_signUpError, _signUpExceptions) ], ), ), ), ], ), ); } void _displayCreateAccount() { widget._displayAccountWidget(AccountStatus.sign_up.index); } } 
Enter fullscreen mode Exit fullscreen mode

confirm_reset_password.dart

To submit the confirmation code and the new password.

 import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:auth_for_flutter/screens/next_screen.dart'; import 'package:flutter/material.dart'; class ConfirmResetPassword extends StatelessWidget { final codeController = TextEditingController(); final emailController = TextEditingController(); final passwordController = TextEditingController(); final String userName; final Function setError; ConfirmResetPassword(this.userName, this.setError); void _confirm_password_reset(BuildContext context) async { try { await Amplify.Auth.confirmPassword( username: this.userName, newPassword: passwordController.text.trim(), confirmationCode: codeController.text.trim()); _go_to_NextScreen(context); } on AuthError catch (e) { setError(e); } } void _go_to_NextScreen(BuildContext context) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) { return NextScreen(); }, ), ); } @override Widget build(BuildContext context) { return Container( // decoration: BoxDecoration(borderRadius: BorderRadius.circular(20)), padding: EdgeInsets.all(5), child: Column( children: [ TextFormField( enableSuggestions: false, decoration: const InputDecoration( icon: Icon(Icons.email), hintText: 'Email', labelText: 'Email *', ), controller: emailController, keyboardType: TextInputType.emailAddress, ), TextFormField( obscureText: true, enableSuggestions: false, autocorrect: false, decoration: const InputDecoration( icon: Icon(Icons.lock), hintText: 'New Password', labelText: 'New Password *', ), controller: passwordController, ), TextFormField( controller: codeController, decoration: const InputDecoration( icon: Icon(Icons.confirmation_number), hintText: 'The code we sent you', labelText: 'Confirmation Code *', )), FlatButton( textColor: Colors.black, // Theme.of(context).primaryColor, color: Colors.amber, onPressed: () => _confirm_password_reset(context), child: Text( 'Reset Password & Sign In', style: TextStyle(fontWeight: FontWeight.bold), ), ), ], ), ); } } 
Enter fullscreen mode Exit fullscreen mode

sign_in.dart

The sign-in operation

 import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:auth_for_flutter/account_screens_enum.dart'; import 'package:auth_for_flutter/screens/next_screen.dart'; import 'package:auth_for_flutter/widgets/error_view.dart'; import 'package:flutter/material.dart'; class SignInView extends StatefulWidget { final Function _displayAccountWidget; const SignInView(this._displayAccountWidget); @override _SignInViewState createState() => _SignInViewState(); } class _SignInViewState extends State<SignInView> { final emailController = TextEditingController(); final passwordController = TextEditingController(); String _signUpError = ""; List<String> _signUpExceptions = []; @override void initState() { super.initState(); } void _setError(AuthError error) { setState(() { _signUpError = error.cause; _signUpExceptions.clear(); error.exceptionList.forEach((el) { _signUpExceptions.add(el.exception); }); }); } void _signIn() async { // Sign out before in case a user is already signed in // If a user is already signed in - Amplify.Auth.signIn will throw an exception try { await Amplify.Auth.signOut(); } on AuthError catch (e) { print(e); } try { SignInResult res = await Amplify.Auth.signIn( username: emailController.text.trim(), password: passwordController.text.trim()); _go_to_NextScreen(context); } on AuthError catch (e) { setState(() { _signUpError = e.cause; _signUpExceptions.clear(); e.exceptionList.forEach((el) { _signUpExceptions.add(el.exception); }); }); } } void _go_to_NextScreen(BuildContext context) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) { return NextScreen(); }, ), ); } @override Widget build(BuildContext context) { return Card( elevation: 2, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Expanded( // wrap your Column in Expanded child: Padding( padding: EdgeInsets.all(10.0), child: Column( children: [ TextFormField( controller: emailController, decoration: const InputDecoration( icon: Icon(Icons.email), hintText: 'Enter your email', labelText: 'Email *', ), ), TextFormField( obscureText: true, controller: passwordController, decoration: const InputDecoration( icon: Icon(Icons.lock), hintText: 'Enter your password', labelText: 'Password *', ), ), const Padding(padding: EdgeInsets.all(10.0)), FlatButton( textColor: Colors.black, // Theme.of(context).primaryColor, color: Colors.amber, onPressed: _signIn, child: const Text( 'Sign In', style: TextStyle(fontWeight: FontWeight.bold), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ FlatButton( height: 5, onPressed: _displayCreateAccount, child: Text( 'Create Account', style: Theme.of(context).textTheme.subtitle2, ), ), FlatButton( height: 5, onPressed: _displayResetPassword, child: Text( 'Reset Password', style: Theme.of(context).textTheme.subtitle2, ), ), ], ), ErrorView(_signUpError, _signUpExceptions) ], ), ), ), ], ), ); } void _displayCreateAccount() { widget._displayAccountWidget(AccountStatus.sign_up.index); } void _displayResetPassword() { widget._displayAccountWidget(AccountStatus.reset_password.index); } } 
Enter fullscreen mode Exit fullscreen mode

error_view.dart

Display the error messages.

 import 'package:flutter/material.dart'; class ErrorView extends StatelessWidget { final String error; final List<String> exceptions; ErrorView(this.error, this.exceptions); @override Widget build(BuildContext context) { // We do not recognize your username and/or password. Please try again. if (error.isNotEmpty || exceptions.length > 0) { return Column(children: <Widget>[ Text('Error: $error', textAlign: TextAlign.center, overflow: TextOverflow.visible, style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).errorColor, )), if (exceptions.length > 0) ...[_showExceptions(context)] ]); } else { return Container(); } } _showExceptions(context) { return Column( children: exceptions .map((item) => new Text(item + " ", style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).errorColor, ))) .toList()); } } 
Enter fullscreen mode Exit fullscreen mode

We will have three screens in the App. Create the screens below in the (lib\screens) folder

loading_screen.dart

 import 'package:flutter/material.dart'; class LoadingScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('BatMan App')), body: Container( color: Color(0xff90CCE6), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( color: Color(0xffE1E5E4), height: 200, child: Image.asset( 'assets/images/applogo.png', fit: BoxFit.cover, ), ), Padding( padding: const EdgeInsets.all(8.0), child: Center( child: CircularProgressIndicator(), ), ), ], ), ), ), ); } } 
Enter fullscreen mode Exit fullscreen mode

main_screen.dart

 import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:auth_for_flutter/account_screens_enum.dart'; import 'package:auth_for_flutter/screens/next_screen.dart'; import 'package:auth_for_flutter/widgets/reset_password.dart'; import 'package:auth_for_flutter/widgets/sign_in.dart'; import 'package:auth_for_flutter/widgets/sign_up.dart'; import 'package:flutter/material.dart'; class MainScreen extends StatefulWidget { @override _MainScreenState createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { var _accountWidget; @override initState() { super.initState(); _fetchSession(); } void _fetchSession() async { // Sign out before in case a user is already signed in try { await Amplify.Auth.signOut(); } on AuthError catch (e) { print(e); } _accountWidget = AccountStatus.sign_in.index; _displayAccountWidget(_accountWidget); } void _go_to_NextScreen(BuildContext context) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) { return NextScreen(); }, ), ); } void _displayAccountWidget(int accountStatus) { setState(() { _accountWidget = AccountStatus.values[accountStatus]; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('BatMan App'), ), body: Container( color: Color(0xffE1E5E4), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( color: Color(0xffE1E5E4), height: 200, child: Image.asset( 'assets/images/applogo.png', fit: BoxFit.cover, ), ), Padding( padding: const EdgeInsets.all(8.0), child: Column(children: [ Visibility( visible: _accountWidget == AccountStatus.sign_in, child: SignInView(_displayAccountWidget), ), Visibility( visible: _accountWidget == AccountStatus.sign_up, child: SignUpView(_displayAccountWidget), ), Visibility( visible: _accountWidget == AccountStatus.reset_password, child: ResetPasswordView(_displayAccountWidget), ), Visibility( visible: _accountWidget == AccountStatus.main_screen, child: NextScreen(), ), ]), ), ], ), ), ), ); } } 
Enter fullscreen mode Exit fullscreen mode

next_screen.dart

 import 'package:auth_for_flutter/screens/main_screen.dart'; import 'package:flutter/material.dart'; class NextScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('BatMan App')), body: Container( color: Color(0xffE1E5E4), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Container( color: Color(0xffE1E5E4), height: 200, child: Image.asset( 'assets/images/applogo.png', fit: BoxFit.cover, ), ), Padding( padding: const EdgeInsets.all(20.0), child: Center( child: Text( 'I\'m BATMAN', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), ), ), ), Padding( padding: const EdgeInsets.all(20.0), child: Center( child: RaisedButton( color: Colors.lightBlue, onPressed: () => _signOut(context), child: Text( 'Sign Out', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, ), ), ), ), ), ], ), ), ), ); } void _signOut(BuildContext context) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) { return MainScreen(); }, ), ); } } 
Enter fullscreen mode Exit fullscreen mode

Run the App

Check the code here

Follow me on Twitter for more tips about #coding, #learning, #technology...etc.

Check my Apps on Google Play

Cover image Obi Onyeador on Unsplash

Top comments (0)