Skip to content

Commit 373b133

Browse files
committed
* Added a new otpExpirationDuration flag, as autoRetrievalTimeOutDuration is a completely different parameter.
1 parent 73a69e9 commit 373b133

File tree

5 files changed

+118
-46
lines changed

5 files changed

+118
-46
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
## [1.0.5] - 10/07/2022
22

33
* BREAKING: Renamed flag timeOutDuration to autoRetrievalTimeOutDuration
4-
* BREAKING: Updated verifyOTP function signature to not take a named argument
4+
* BREAKING: Renamed verifyOTP to verifyOtp
5+
* BREAKING: Updated verifyOtp function signature to not take a named argument, and accept otp as a positional argument
6+
* Added a new otpExpirationDuration flag, as autoRetrievalTimeOutDuration is a completely different parameter.
57
* Added callback onCodeSent and flag signOutOnSuccessfulVerification
68
* Added isSendingCode flag to controller
79
* Optimized code to reduce number of rebuilds

README.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,8 @@ class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
290290
child: FirebasePhoneAuthHandler(
291291
phoneNumber: widget.phoneNumber,
292292
signOutOnSuccessfulVerification: false,
293-
autoRetrievalTimeOutDuration: const Duration(seconds: 60),
293+
autoRetrievalTimeOutDuration: const Duration(seconds: 30),
294+
otpExpirationDuration: const Duration(seconds: 60),
294295
onCodeSent: () {
295296
log(VerifyPhoneNumberScreen.id, msg: 'OTP sent!');
296297
},
@@ -329,16 +330,16 @@ class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
329330
actions: [
330331
if (controller.codeSent)
331332
TextButton(
332-
onPressed: controller.timerIsActive
333-
? null
334-
: () async {
333+
onPressed: controller.isOtpExpired
334+
? () async {
335335
log(VerifyPhoneNumberScreen.id, msg: 'Resend OTP');
336336
await controller.sendOTP();
337-
},
337+
}
338+
: null,
338339
child: Text(
339-
controller.timerIsActive
340-
? '${controller.timerCount.inSeconds}s'
341-
: 'Resend',
340+
controller.isOtpExpired
341+
? 'Resend'
342+
: '${controller.otpExpirationTimeLeft.inSeconds}s',
342343
style: const TextStyle(color: Colors.blue, fontSize: 18),
343344
),
344345
),
@@ -370,7 +371,7 @@ class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
370371
),
371372
const SizedBox(height: 10),
372373
const Divider(),
373-
if (controller.timerIsActive)
374+
if (controller.isListeningForOtpAutoRetrieve)
374375
Column(
375376
children: const [
376377
CustomLoader(),
@@ -403,11 +404,11 @@ class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
403404
onFocusChange: (hasFocus) async {
404405
if (hasFocus) await _scrollToBottomOnKeyboardOpen();
405406
},
406-
onSubmit: (enteredOTP) async {
407-
final isValidOTP =
408-
await controller.verifyOTP(enteredOTP);
407+
onSubmit: (enteredOtp) async {
408+
final isValidOtp =
409+
await controller.verifyOtp(enteredOtp);
409410
// Incorrect OTP
410-
if (!isValidOTP) {
411+
if (!isValidOtp) {
411412
showSnackBar('The entered OTP is invalid!');
412413
}
413414
},

example/lib/screens/verify_phone_number_screen.dart

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
6767
child: FirebasePhoneAuthHandler(
6868
phoneNumber: widget.phoneNumber,
6969
signOutOnSuccessfulVerification: false,
70-
autoRetrievalTimeOutDuration: const Duration(seconds: 60),
70+
autoRetrievalTimeOutDuration: const Duration(seconds: 30),
71+
otpExpirationDuration: const Duration(seconds: 60),
7172
onCodeSent: () {
7273
log(VerifyPhoneNumberScreen.id, msg: 'OTP sent!');
7374
},
@@ -106,16 +107,16 @@ class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
106107
actions: [
107108
if (controller.codeSent)
108109
TextButton(
109-
onPressed: controller.timerIsActive
110-
? null
111-
: () async {
110+
onPressed: controller.isOtpExpired
111+
? () async {
112112
log(VerifyPhoneNumberScreen.id, msg: 'Resend OTP');
113113
await controller.sendOTP();
114-
},
114+
}
115+
: null,
115116
child: Text(
116-
controller.timerIsActive
117-
? '${controller.timerCount.inSeconds}s'
118-
: 'Resend',
117+
controller.isOtpExpired
118+
? 'Resend'
119+
: '${controller.otpExpirationTimeLeft.inSeconds}s',
119120
style: const TextStyle(color: Colors.blue, fontSize: 18),
120121
),
121122
),
@@ -147,7 +148,7 @@ class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
147148
),
148149
const SizedBox(height: 10),
149150
const Divider(),
150-
if (controller.timerIsActive)
151+
if (controller.isListeningForOtpAutoRetrieve)
151152
Column(
152153
children: const [
153154
CustomLoader(),
@@ -180,11 +181,11 @@ class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
180181
onFocusChange: (hasFocus) async {
181182
if (hasFocus) await _scrollToBottomOnKeyboardOpen();
182183
},
183-
onSubmit: (enteredOTP) async {
184-
final isValidOTP =
185-
await controller.verifyOTP(enteredOTP);
184+
onSubmit: (enteredOtp) async {
185+
final isValidOtp =
186+
await controller.verifyOtp(enteredOtp);
186187
// Incorrect OTP
187-
if (!isValidOTP) {
188+
if (!isValidOtp) {
188189
showSnackBar('The entered OTP is invalid!');
189190
}
190191
},

lib/src/auth_controller.dart

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class FirebasePhoneAuthController extends ChangeNotifier {
88
Provider.of<FirebasePhoneAuthController>(context, listen: listen);
99

1010
/// {@macro autoRetrievalTimeOutDuration}
11-
static const kAutoRetrievalTimeOutDuration = Duration(seconds: 60);
11+
static const kAutoRetrievalTimeOutDuration = Duration(minutes: 1);
1212

1313
/// Firebase auth instance using the default [FirebaseApp].
1414
static final FirebaseAuth _auth = FirebaseAuth.instance;
@@ -31,7 +31,10 @@ class FirebasePhoneAuthController extends ChangeNotifier {
3131
String? _verificationId;
3232

3333
/// Timer object for SMS auto-retrieval.
34-
Timer? _timer;
34+
Timer? _otpAutoRetrievalTimer;
35+
36+
/// Timer object for OTP expiration.
37+
Timer? _otpExpirationTimer;
3538

3639
/// Whether OTP to the given phoneNumber is sent or not.
3740
bool codeSent = false;
@@ -63,26 +66,54 @@ class FirebasePhoneAuthController extends ChangeNotifier {
6366
required bool signOutOnSuccessfulVerification,
6467
RecaptchaVerifier? recaptchaVerifierForWeb,
6568
Duration autoRetrievalTimeOutDuration = kAutoRetrievalTimeOutDuration,
69+
Duration otpExpirationDuration = kAutoRetrievalTimeOutDuration,
6670
}) {
6771
_phoneNumber = phoneNumber;
6872
_signOutOnSuccessfulVerification = signOutOnSuccessfulVerification;
6973
_onLoginSuccess = onLoginSuccess;
7074
_onCodeSent = onCodeSent;
7175
_onLoginFailed = onLoginFailed;
7276
_autoRetrievalTimeOutDuration = autoRetrievalTimeOutDuration;
77+
_otpExpirationDuration = otpExpirationDuration;
7378
if (kIsWeb) _recaptchaVerifierForWeb = recaptchaVerifierForWeb;
7479
}
7580

76-
/// After a [Duration] of [timerCount], the library no more waits for SMS auto-retrieval.
77-
Duration get timerCount =>
78-
Duration(seconds: _autoRetrievalTimeOutDuration.inSeconds - (_timer?.tick ?? 0));
81+
/// [otpExpirationTimeLeft] can be used to display a reverse countdown, starting from
82+
/// [_otpExpirationDuration.inSeconds]s till 0, and can show the resend
83+
/// button, to let user request a new OTP.
84+
Duration get otpExpirationTimeLeft {
85+
final otpTickDuration = Duration(
86+
seconds: (_otpExpirationTimer?.tick ?? 0),
87+
);
88+
return _otpExpirationDuration - otpTickDuration;
89+
}
90+
91+
/// [autoRetrievalTimeLeft] can be used to display a reverse countdown, starting from
92+
/// [_autoRetrievalTimeOutDuration.inSeconds]s till 0, and can show the
93+
/// the listening for OTP view, and also the time left.
94+
///
95+
/// After this timer is exhausted, the device no longer tries to auto-fetch
96+
/// the OTP, and requires user to manually enter it.
97+
Duration get autoRetrievalTimeLeft {
98+
final otpTickDuration = Duration(
99+
seconds: (_otpAutoRetrievalTimer?.tick ?? 0),
100+
);
101+
return _autoRetrievalTimeOutDuration - otpTickDuration;
102+
}
103+
104+
/// Whether the otp has expired or not.
105+
bool get isOtpExpired => !(_otpExpirationTimer?.isActive ?? false);
79106

80-
/// Whether the timer is active or not.
81-
bool get timerIsActive => _timer?.isActive ?? false;
107+
/// Whether the otp retrieval timer is active or not.
108+
bool get isListeningForOtpAutoRetrieve =>
109+
_otpAutoRetrievalTimer?.isActive ?? false;
82110

83111
/// {@macro autoRetrievalTimeOutDuration}
84112
static Duration _autoRetrievalTimeOutDuration = kAutoRetrievalTimeOutDuration;
85113

114+
/// {@macro otpExpirationDuration}
115+
static Duration _otpExpirationDuration = kAutoRetrievalTimeOutDuration;
116+
86117
/// Verify the OTP sent to [_phoneNumber] and login user is OTP was correct.
87118
///
88119
/// Returns true if the [otp] passed was correct and the user was logged in successfully.
@@ -93,9 +124,10 @@ class FirebasePhoneAuthController extends ChangeNotifier {
93124
///
94125
/// Also, [_onLoginFailed] is called with [FirebaseAuthException]
95126
/// object to handle the error.
96-
Future<bool> verifyOTP(String otp) async {
127+
Future<bool> verifyOtp(String otp) async {
97128
if ((!kIsWeb && _verificationId == null) ||
98129
(kIsWeb && _webConfirmationResult == null)) return false;
130+
99131
try {
100132
if (kIsWeb) {
101133
final userCredential = await _webConfirmationResult!.confirm(otp);
@@ -148,7 +180,6 @@ class FirebasePhoneAuthController extends ChangeNotifier {
148180
_forceResendingToken = forceResendingToken;
149181
codeSent = true;
150182
_onCodeSent?.call();
151-
notifyListeners();
152183
_setTimer();
153184
}
154185

@@ -187,7 +218,7 @@ class FirebasePhoneAuthController extends ChangeNotifier {
187218
}
188219

189220
/// Called when the otp is verified either automatically (OTP auto fetched)
190-
/// or [verifyOTP] was called with the correct OTP.
221+
/// or [verifyOtp] was called with the correct OTP.
191222
///
192223
/// If true is returned that means the user was logged in successfully.
193224
///
@@ -225,12 +256,28 @@ class FirebasePhoneAuthController extends ChangeNotifier {
225256

226257
/// Set timer after code sent.
227258
void _setTimer() {
228-
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
229-
if (timer.tick == _autoRetrievalTimeOutDuration.inSeconds) _timer?.cancel();
230-
try {
231-
notifyListeners();
232-
} catch (_) {}
233-
});
259+
_otpExpirationTimer = Timer.periodic(
260+
const Duration(seconds: 1),
261+
(timer) {
262+
if (timer.tick == _otpExpirationDuration.inSeconds) {
263+
_otpExpirationTimer?.cancel();
264+
}
265+
try {
266+
notifyListeners();
267+
} catch (_) {}
268+
},
269+
);
270+
_otpAutoRetrievalTimer = Timer.periodic(
271+
const Duration(seconds: 1),
272+
(timer) {
273+
if (timer.tick == _autoRetrievalTimeOutDuration.inSeconds) {
274+
_otpAutoRetrievalTimer?.cancel();
275+
}
276+
try {
277+
notifyListeners();
278+
} catch (_) {}
279+
},
280+
);
234281
notifyListeners();
235282
}
236283

@@ -253,10 +300,11 @@ class FirebasePhoneAuthController extends ChangeNotifier {
253300
_onCodeSent = null;
254301
_signOutOnSuccessfulVerification = false;
255302
_forceResendingToken = null;
256-
_timer?.cancel();
257-
_timer = null;
303+
_otpExpirationTimer?.cancel();
304+
_otpExpirationTimer = null;
258305
_phoneNumber = null;
259306
_autoRetrievalTimeOutDuration = kAutoRetrievalTimeOutDuration;
307+
_otpExpirationDuration = kAutoRetrievalTimeOutDuration;
260308
_verificationId = null;
261309
}
262310
}

lib/src/auth_handler.dart

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class FirebasePhoneAuthHandler extends StatefulWidget {
1919
this.onCodeSent,
2020
this.signOutOnSuccessfulVerification = false,
2121
this.autoRetrievalTimeOutDuration = FirebasePhoneAuthController.kAutoRetrievalTimeOutDuration,
22+
this.otpExpirationDuration = FirebasePhoneAuthController.kAutoRetrievalTimeOutDuration,
2223
this.recaptchaVerifierForWebProvider,
2324
}) : super(key: key);
2425

@@ -60,7 +61,7 @@ class FirebasePhoneAuthHandler extends StatefulWidget {
6061
/// essential user information.
6162
///
6263
/// The boolean provided is whether the OTP was auto verified or
63-
/// verified manually by calling [verifyOTP].
64+
/// verified manually by calling [verifyOtp].
6465
///
6566
/// True if auto verified and false is verified manually.
6667
///
@@ -82,11 +83,29 @@ class FirebasePhoneAuthHandler extends StatefulWidget {
8283
///
8384
/// Maximum allowed value is 2 minutes.
8485
///
86+
/// NOTE: The user can still use the OTP to sign in after
87+
/// [autoRetrievalTimeOutDuration] duration, but the device
88+
/// will not try to auto-fetch the OTP after this set duration.
89+
///
8590
/// Defaults to [FirebasePhoneAuthController.kAutoRetrievalTimeOutDuration].
8691
///
8792
/// {@endtemplate}
8893
final Duration autoRetrievalTimeOutDuration;
8994

95+
/// {@template otpExpirationDuration}
96+
///
97+
/// The OTP expiration duration, can be used to display a timer, and show
98+
/// a resend button, to resend the OTP.
99+
///
100+
/// Firebase does not document if the OTP ever expires, or anything
101+
/// about it's validity. Hence, this can be used to show a timer, or force
102+
/// user to request a new otp after a set duration.
103+
///
104+
/// Defaults to [FirebasePhoneAuthController.kAutoRetrievalTimeOutDuration].
105+
///
106+
/// {@endtemplate}
107+
final Duration otpExpirationDuration;
108+
90109
/// {@template recaptchaVerifierForWeb}
91110
///
92111
/// Custom reCAPTCHA for web-based authentication.
@@ -141,6 +160,7 @@ class _FirebasePhoneAuthHandlerState extends State<FirebasePhoneAuthHandler> {
141160
onLoginSuccess: widget.onLoginSuccess,
142161
onLoginFailed: widget.onLoginFailed,
143162
autoRetrievalTimeOutDuration: widget.autoRetrievalTimeOutDuration,
163+
otpExpirationDuration: widget.otpExpirationDuration,
144164
onCodeSent: widget.onCodeSent,
145165
signOutOnSuccessfulVerification: widget.signOutOnSuccessfulVerification,
146166
recaptchaVerifierForWeb: captcha,

0 commit comments

Comments
 (0)