Flutter: Check if existing user or new user after OTP Authentication - android

I am trying to check after the OTP Process whether a user is a returning user that exists in my firebase or a new user and if so should be taken to a page where he inputs his details in it.
I have already made the user go to the chat screen after verifying OTP, but I want it to check first to see if the user is existing or not so that I get extra details before he continues to the chat screen.
This is the authentication function that I use.
Future<void> phoneSignIn(
BuildContext context,
String phoneNumber,
) async {
TextEditingController codeController = TextEditingController();
try {
// FOR ANDROID, IOS
final newUser = await _auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: const Duration(seconds: 120),
// Automatic handling of the SMS code
verificationCompleted: (PhoneAuthCredential credential) async {
// !!! works only on android !!!
await _auth.signInWithCredential(credential);
},
// Displays a message when verification fails
verificationFailed: (e) {
showSnackBar(context, e.message!);
},
// Displays a dialog box when OTP is sent
codeSent: ((String verificationId, int? resendToken) async {
showOTPDialog(
codeController: codeController,
context: context,
confirmOTP: () async {
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: codeController.text.trim(),
);
// !!! Works only on Android, iOS !!!
await _auth.signInWithCredential(credential);
Navigator.of(context).pop(); // Remove the dialog box
},
);
}),
codeAutoRetrievalTimeout: (String verificationId) {
// Auto-resolution timed out...
},
);
} catch (e) {
print(e);
}
}
and this is the OTP simple dialog to input the OTP code received
void showOTPDialog({
required BuildContext context,
required TextEditingController codeController,
required VoidCallback confirmOTP,
}) {
showDialog(
context:context,
barrierDismissible: false,
builder: (context)=> AlertDialog(
title: const Text("Enter OTP"),
content: Column(
mainAxisSize: MainAxisSize.min,
children:<Widget>[
TextField(
controller: codeController,
)
]
),
actions: <Widget>[
TextButton(
child:const Text('Confirm Code'),
onPressed: confirmOTP,
)
],
),
);
}```

This is how you can do
auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted: (PhoneAuthCredential credential) async {
final userCredential = await auth.signInWithCredential(credential);
final isNew = userCredential.additionalUserInfo?.isNewUser ?? false;
print('isNew $isNew'); // This is where you will get the value
},
verificationFailed: (FirebaseAuthException e) {},
codeSent: onOtpSentHandler,
codeAutoRetrievalTimeout: (String verificationId) {},
);

Related

The format of the phone number provided is incorrent Please enter the phone number in a format that can parsed into E.164 format

I am trying to log in through my phone number and OTP using firebase and I am using the IntlPhoneField package to enter the number and select desired country code.
Here is how I am doing it
IntlPhoneField(
controller: phoneController,
showCountryFlag: false,
decoration: InputDecoration(
isDense: true,
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: AppColors.greyColor),
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: AppColors.greyColor),
),
),
initialCountryCode: 'PK',
onChanged: (phone) {
setState(() {
dialCodeDigit = phone.countryCode;
});
},
)
And i am sending this code and phone number to provider like that
if (_formKey.currentState!.validate()){
AddNewDialogBox.dialog(context, true);
context
.read<LoginPhoneProvider>()
.phoneSignInn(context, phoneController.text, dialCodeDigit);
print(dialCodeDigit);
}
here is my code for getting OTP using provider state management and getting phone number and country code from login screen
Future<void> phoneSignInn(
BuildContext context,
String phoneNumber,
String countryCode,
// String countryCOde,
) async {
TextEditingController codeController = TextEditingController();
if (kIsWeb) {
// !!! Works only on web !!!
ConfirmationResult result =
await _auth.signInWithPhoneNumber(phoneNumber);
// Diplay Dialog Box To accept OTP
showOTPDialog(
phoneNumber: phoneNumber + countryCode,
//countryCode: countryCode,
codeController: codeController,
context: context,
onPressed: () async {
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: result.verificationId,
smsCode: codeController.text.trim(),
);
await _auth.signInWithCredential(credential);
Navigator.of(context).pop(); // Remove the dialog box
},
);
} else {
// FOR ANDROID, IOS
await _auth.verifyPhoneNumber(
phoneNumber: phoneNumber + countryCode,
// Automatic handling of the SMS code
verificationCompleted: (PhoneAuthCredential credential) async {
// !!! works only on android !!!
await _auth.signInWithCredential(credential);
},
// Displays a message when verification fails
verificationFailed: (e) {
showSnackBar(context, e.message!);
},
// Displays a dialog box when OTP is sent
codeSent: ((String verificationId, int? resendToken) async {
showOTPDialog(
phoneNumber: phoneNumber + countryCode,
// countryCode: countryCode,
codeController: codeController,
context: context,
onPressed: () async {
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: codeController.text.trim(),
);
// !!! Works only on Android, iOS !!!
await _auth.signInWithCredential(credential);
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const DriverSginUpScreen()));
// Navigator.of(context).pop(); // Remove the dialog box
},
);
}),
codeAutoRetrievalTimeout: (String verificationId) {
// Auto-resolution timed out...
},
);
}
}
incase if you need to know about the dialog that I show after receiving OTP here is the code of OTP dialog
void showOTPDialog({
required BuildContext context,
required TextEditingController codeController,
required VoidCallback onPressed,
required String phoneNumber,
}) {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => SizedBox(
width: double.infinity,
child: AlertDialog(
title: Text("Enter 6 Digit OTP send to\n$phoneNumber"),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
PinCodeTextField(
pinTheme: PinTheme(
shape: PinCodeFieldShape.box,
borderRadius: BorderRadius.circular(5),
fieldHeight: 60,
fieldWidth: 30,
activeFillColor:
Colors.white,
),
appContext: context,
length: 6,
controller: codeController,
onChanged: (value) {
print(value);
}
),
],
),
actions: <Widget>[
TextButton(
onPressed: onPressed,
child: const Text("Done"),
)
],
),
),
);
}
But I am getting an error related to phone format I don't know what I am doing wrong and how to resolve it here is an error that I receive after entering the phone number:
The format of the phone number provided is incorrent
Please enter the phone number in a format that can
parsed into E.164 format. E.164 phone numbers are
written in the format [+|country code][subscriber
number Including area codel. Invalid format. 1

firebase i have a problem of authentication

I am facing a problem in Firebase that sometimes it sends verify to the number and sometimes it does not send even though it did not exceed the limit allowed for sending in Firebase
my code
FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: phoneNumber,
verificationCompleted: (PhoneAuthCredential credential) {
},
verificationFailed: (FirebaseAuthException e) {
setState(() {
isLoading=false;
});
print(e.message);
if (e.code == 'invalid-phone-number') {
print('The provided phone number is not valid.');
}
},
codeSent: (String verificationId, int? resendToken) async {
setState(() {
isLoading=false;
});
await Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return verifyCode(
verification: verificationId.toString(),
registerModel: user,
);
}));
},
timeout: const Duration(seconds: 60),
codeAutoRetrievalTimeout: (String verificationId) {},
);
}}
I'm using Flutter.

Should I pass the google sign-in variables to other class?

I made a log-in page and made a button for google log-in like this :
Future<String> login_google() async{
FirebaseAuth auth = FirebaseAuth.instance;
GoogleSignIn googleSignIn = GoogleSignIn();
GoogleSignInAccount? account = await googleSignIn.signIn();
GoogleSignInAuthentication authentication = await account!.authentication;
AuthCredential credential = GoogleAuthProvider.credential(
idToken: authentication.idToken,
accessToken: authentication.accessToken);
final authResult = await auth.signInWithCredential(credential);
final user = authResult.user;
print (user?.uid);
print (user?.email);
print('google log-in completed');
return Future.value(user?.uid);
}
...
class _login_pageState extends State<login_page> {
#override
Widget build(BuildContext context) {
// String uid_r = await login_google();
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
toolbarHeight: 1.0,
),
body:Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image( image: AssetImage('asset/logo.png'),),
ElevatedButton(onPressed: (){
login_kakao();
Navigator.of(context).push(MaterialPageRoute(builder: (context) =>main_bone(uid:'')));
}, child: Text('Kakao Log-In')),
ElevatedButton(onPressed: (){
// login_kakao();
final uid = login_google().then(
(value){
Navigator.of(context).push(MaterialPageRoute(builder: (context) =>main_bone(uid: value))); => The button for LOG-IN .
}
);
}, child: Text('Google Log-In'))
],
),
);
}
}
Here I succeeded in log-in and passed only uid thinking that only uid is required. And My firestore rules for accessing is following :
match /post/{document=**}
{
allow read;
allow write:if
auth.uid != null;
}
On the next page, None of the data in collection(post) were shown, which means the log in has disabled on the next class .
Should I pass the whole variables for authentication to next class, for example, auth, credential ?
its called inherited widget, there is a lot method to make your entire app authenticated, not just one class. make a stream builder, listen firebase_auth.authStateChanges()
or you can use a package ( state management ) :
like
flutter_bloc
or
i prefer goRouter
check this video, its chris itself explain how to use it, its even better; work at flutter web too
take look at mine for your reference :
import 'dart:ui';
import 'package:cms_prototype/auth/auth.dart';
import 'package:cms_prototype/home/home.dart';
import 'package:cms_prototype/login/login.dart';
import 'package:cms_prototype/theme.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: const FirebaseOptions(
apiKey: "********",
authDomain: "",
appId: "",
measurementId: "",
storageBucket: "**.com",
messagingSenderId: "**",
projectId: "**"));
final auth = AuthRepo();
await auth.user.first;
final router = GoRouter(
urlPathStrategy: UrlPathStrategy.path,
redirect: (GoRouterState state){
if(auth.currentUser.isEmpty && state.location != '/login'){
return '/login';
}
if(auth.currentUser.isNotEmpty && state.location == '/login'){
return '/';
}
return null;
},
refreshListenable: GoRouterRefreshStream(auth.user),
routes: [
GoRoute(
name: 'home',
path: '/',
pageBuilder: (context, state) => MaterialPage(
key: state.pageKey,
child: const HomePage())),
GoRoute(
name: 'login',
path: '/login',
pageBuilder: (context, state) => MaterialPage(
key: state.pageKey,
child: LoginPage(authRepo: auth,))),
],
errorPageBuilder: (context, state) => const MaterialPage(
child: Scaffold(
body: Center(
child: Text("404 Error"),
),
)));
runApp(MyApp(router: router,authRepo: auth,));
}
class MyApp extends StatelessWidget {
final GoRouter router;
final AuthRepo authRepo;
const MyApp({Key? key, required this.router, required this.authRepo}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
debugShowCheckedModeBanner: false,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown
},
),
title: 'CMS Prototype',
theme: appTheme,
);
}
}
auth repo class : ---->
class AuthRepo {
final AuthCache _cache;
final firebase_auth.FirebaseAuth _firebaseAuth;
final GoogleSignIn _googleSignIn;
AuthRepo({AuthCache? cache, firebase_auth.FirebaseAuth? firebaseAuth, GoogleSignIn? googleSignIn})
: _cache = cache?? AuthCache(),
_firebaseAuth = firebaseAuth ?? firebase_auth.FirebaseAuth.instance,
_googleSignIn = googleSignIn ?? GoogleSignIn.standard();
Stream<User> get user {
return _firebaseAuth.authStateChanges().map((fUser){
final user = fUser == null ? User.empty : fUser.toUser;
_cache.write(key: user.id, value: user);
return user;
});
}
User get currentUser {
final key = _firebaseAuth.currentUser != null? _firebaseAuth.currentUser!.uid : '';
return _cache.read(key: key) ?? User.empty;
}
Future<void> signUp({required String email, required String password}) async {
try {
await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
} on firebase_auth.FirebaseAuthException catch (e) {
throw SignUpWithEmailAndPasswordFailure.fromCode(e.code);
} catch (_) {
throw const SignUpWithEmailAndPasswordFailure();
}
}
//login with google (GOOGLE SIGN IN)
Future<void> loginWithGoogle() async {
try {
late final firebase_auth.AuthCredential credential;
if(kIsWeb){
final googleProvider = firebase_auth.GoogleAuthProvider();
final userCredential = await _firebaseAuth.signInWithPopup(googleProvider);
credential = userCredential.credential!;
}else{
final googleUser = await _googleSignIn.signIn();
final googleAuth = await googleUser!.authentication;
credential = firebase_auth.GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken
);
}
await _firebaseAuth.signInWithCredential(credential);
}
on firebase_auth.FirebaseAuthException catch (e){
throw LogInWithGoogleFailure.fromCode(e.code);
}
catch (e){
throw const LogInWithGoogleFailure();
}
}
//email dan password
Future<LogInWithEmailAndPasswordFailure?> loginWithEmailAndPassword ({required String email, required String password}) async{
try{
await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password
);
}
on firebase_auth.FirebaseAuthException catch (e){
//return e.code;
return LogInWithEmailAndPasswordFailure.fromCode(e.code);
}
catch (_){
//return 'masalah jaringan terdeteksi';
return const LogInWithEmailAndPasswordFailure();
}
return null;
}
Future<void> logOut() async {
try {
await Future.wait([
_firebaseAuth.signOut(),
_googleSignIn.signOut(),
]);
} catch (_) {
throw LogOutFailure();
}
}
}
You should be able to access all the data for the current user using FirebaseAuth.instance.currentUser. Use that to access the current user and pass any data to backend using that user's credentials and token.
final user = FirebaseAuth.instance.currentUser;

how do I verify a new user's email in Flutter and Firebase before granting them access to the app?

I am trying to have new users create an account, receive a verification email and then only be granted access to the app once they have clicked the verification link sent by Firebase. I am not getting any errors but there are clearly shortcomings in my code. The new user information is accepted and stored by Firestore and a verification email is sent but once the verification link is clicked the user remains on the Verify page instead of navigating to the HomePage (called Jobs Page in this code).
I cannot figure this out after days of trying so any help would be greatly appreciated. I have included several pages of my code. Thanks in advance!
abstract class AuthBase {
User? get currentUser;
Stream<User?> authStateChanges();
Future<User?> signInWithEmailAndPassword(String email, String password);
Future<void> createUserWithEmailAndPasswordVerify(
String email, String password);
Future<void> signOut();
}
class Auth implements AuthBase {
// Value that retrieves an instance of the FirebaseAuth object. This is used for managing users between the app and the Firebase backend
final _firebaseAuth = FirebaseAuth.instance;
#override
Stream<User?> authStateChanges() => _firebaseAuth.authStateChanges();
#override
User? get currentUser => _firebaseAuth.currentUser;
#override
Future<User?> signInWithEmailAndPassword(
String email, String password) async {
final userCredential = await _firebaseAuth.signInWithCredential(
EmailAuthProvider.credential(
email: email,
password: password,
),
);
return userCredential.user;
}
#override
Future<User?> createUserWithEmailAndPasswordVerify(
String email, String password) async {
final userCredential = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
try {
await userCredential.user?.sendEmailVerification();
} catch (e) {
print(
'An error occurred while trying to send email verification',
);
}
return userCredential.user;
}
#override
Future<void> signOut() async {
await GoogleSignIn().signOut();
await _firebaseAuth.signOut();
}
}
class VerifyPage extends StatefulWidget {
const VerifyPage({
Key? key,
}) : super(key: key);
#override
State<VerifyPage> createState() => _VerifyPageState();
}
class _VerifyPageState extends State<VerifyPage> {
AuthBase? auth;
Timer? timer;
User? user;
bool isUserEmailVerified = false;
#override
void initState() {
super.initState();
user = auth?.currentUser;
user?.sendEmailVerification();
timer = Timer.periodic(
const Duration(
seconds: 5,
),
(timer) {
checkEmailVerified();
},
);
}
Future<void> checkEmailVerified() async {
user = auth?.currentUser;
await user?.reload();
final signedInUser = user;
if (signedInUser != null && signedInUser.emailVerified) {
timer?.cancel();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const JobsPage(),
),
);
}
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Container(),
title: const Text('Verify Email'),
),
body: const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'An email has just been sent to your email. Click on the link provided to complete registration.',
style: TextStyle(
fontSize: 20.0,
),
),
),
),
);
}
}
code from email sign in form
Future<void> _submitVerify() async {
try {
await model.submitVerify(context);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const VerifyPage(),
),
);
} on FirebaseAuthException catch (e) {
showExceptionAlertDialog(
context,
exception: e,
title: 'Sign In Failed',
);
}
}
I think you just don't initiate Auth()
late AuthBase auth;
late Timer timer;
User? user;
bool isUserEmailVerified = false;
#override
void initState() {
super.initState();
auth = Auth();
user = auth.currentUser;
user?.sendEmailVerification();
timer = Timer.periodic(
const Duration(
seconds: 5,
),
(timer) {
checkEmailVerified();
},
);
}

Cloud Firestore Document Check after Phone Authentication

I will try to explain it as clearly as possible
I wanted to add a check when verification is complete. In that the check is supposed to be like: Check if there is a document with document id as the user UID which has authenticated. If it is there then go to the home. If it is not there then create a document using updateData class that I have created already and then go to the home page
Here is my code for phone authentication
Future phoneAuthentication(
String fullName,
String phoneNumber,
String phoneIsoCode,
String nonInternationalNumber,
String profilePicture,
String verificationCode,
BuildContext context,
) async {
_auth.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: Duration(seconds: 0),
verificationCompleted: (AuthCredential authCredential) async {
_auth.signInWithCredential(authCredential).then(
(UserCredential result) async {
User user = result.user;
await DatabaseService(uid: user.uid).updateUserData(
fullName,
phoneNumber,
phoneIsoCode,
nonInternationalNumber,
profilePicture,
);
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => CustomerDashboard(),
),
(route) => false,
);
},
).catchError(
(e) {
return null;
},
);
},
verificationFailed: (FirebaseAuthException exception) {
return "Error";
},
codeSent: (String verificationId, [int forceResendingToken]) {
var _credential = PhoneAuthProvider.credential(
verificationId: verificationId,
smsCode: verificationCode,
);
_auth.signInWithCredential(_credential).then(
(UserCredential result) async {
User user = result.user;
await DatabaseService(uid: user.uid).updateUserData(
fullName,
phoneNumber,
phoneIsoCode,
nonInternationalNumber,
profilePicture,
);
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => CustomerDashboard(),
),
(route) => false,
);
},
).catchError(
(e) {},
);
},
codeAutoRetrievalTimeout: (String verificationId) {
verificationId = verificationId;
},
);
}
Please help me how I am supposed to add this check.
I copied & adapted this code here from this Stack Overflow Question
DocumentSnapshot ds = await YOUR_DB_REFERENCE_IDENTIFIER.collection("YOUR_COLLECTION_NAME").document(user.uid).get();
return ds.exists;

Categories

Resources