@@ -217,155 +217,176 @@ void _verifyPhoneNumber() async {
217217
218218#### Sample Usage
219219``` dart
220- import 'package:firebase_core/firebase_core.dart';
221220import 'package:firebase_phone_auth_handler/firebase_phone_auth_handler.dart';
222221import 'package:flutter/material.dart';
222+ import 'package:phone_auth_handler_demo/screens/home_screen.dart';
223+ import 'package:phone_auth_handler_demo/utils/helpers.dart';
224+ import 'package:phone_auth_handler_demo/widgets/custom_loader.dart';
225+ import 'package:phone_auth_handler_demo/widgets/pin_input_field.dart';
223226
224- void main() async {
225- WidgetsFlutterBinding.ensureInitialized();
226- await Firebase.initializeApp();
227- runApp(_MainApp());
227+ class VerifyPhoneNumberScreen extends StatefulWidget {
228+ static const id = 'VerifyPhoneNumberScreen';
229+
230+ final String phoneNumber;
231+
232+ const VerifyPhoneNumberScreen({
233+ Key? key,
234+ required this.phoneNumber,
235+ }) : super(key: key);
236+
237+ @override
238+ State<VerifyPhoneNumberScreen> createState() =>
239+ _VerifyPhoneNumberScreenState();
228240}
229241
230- class _MainApp extends StatelessWidget {
242+ class _VerifyPhoneNumberScreenState extends State<VerifyPhoneNumberScreen>
243+ with WidgetsBindingObserver {
244+ bool isKeyboardVisible = false;
245+
246+ late final ScrollController scrollController;
247+
231248 @override
232- Widget build(BuildContext context) {
233- return FirebasePhoneAuthProvider(
234- child: MaterialApp(
235- debugShowCheckedModeBanner: false,
236- home: VerifyPhoneNumberScreen(phoneNumber: "+919876543210"),
237- ),
238- );
249+ void initState() {
250+ scrollController = ScrollController();
251+ WidgetsBinding.instance?.addObserver(this);
252+ super.initState();
239253 }
240- }
241254
242- // ignore: must_be_immutable
243- class VerifyPhoneNumberScreen extends StatelessWidget {
244- final String phoneNumber;
255+ @override
256+ void dispose() {
257+ WidgetsBinding.instance?.removeObserver(this);
258+ scrollController.dispose();
259+ super.dispose();
260+ }
245261
246- String? _enteredOTP;
262+ @override
263+ void didChangeMetrics() {
264+ final bottomViewInsets = WidgetsBinding.instance!.window.viewInsets.bottom;
265+ isKeyboardVisible = bottomViewInsets > 0;
266+ }
247267
248- VerifyPhoneNumberScreen({
249- Key? key,
250- required this.phoneNumber,
251- }) : super(key: key);
268+ // scroll to bottom of screen, when pin input field is in focus.
269+ Future<void> _scrollToBottomOnKeyboardOpen() async {
270+ while (!isKeyboardVisible) {
271+ await Future.delayed(const Duration(milliseconds: 50));
272+ }
273+
274+ await Future.delayed(const Duration(milliseconds: 250));
252275
253- void _showSnackBar(BuildContext context, String text) {
254- ScaffoldMessenger.of(context).showSnackBar(
255- SnackBar(content: Text(text)),
276+ await scrollController.animateTo(
277+ scrollController.position.maxScrollExtent,
278+ duration: const Duration(milliseconds: 250),
279+ curve: Curves.easeIn,
256280 );
257281 }
258282
259283 @override
260284 Widget build(BuildContext context) {
261285 return SafeArea(
262286 child: FirebasePhoneAuthHandler(
263- phoneNumber: phoneNumber,
264- timeOutDuration: const Duration(seconds: 60),
287+ phoneNumber: widget.phoneNumber,
265288 onLoginSuccess: (userCredential, autoVerified) async {
266- _showSnackBar(
267- context,
268- 'Phone number verified successfully!',
289+ log(
290+ VerifyPhoneNumberScreen.id,
291+ msg: autoVerified
292+ ? 'OTP was fetched automatically!'
293+ : 'OTP was verified manually!',
269294 );
270295
271- debugPrint(
272- autoVerified
273- ? "OTP was fetched automatically"
274- : "OTP was verified manually",
296+ showSnackBar('Phone number verified successfully!');
297+
298+ log(
299+ VerifyPhoneNumberScreen.id,
300+ msg: 'Login Success UID: ${userCredential.user?.uid}',
275301 );
276302
277- debugPrint("Login Success UID: ${userCredential.user?.uid}");
278- },
279- onLoginFailed: (authException) {
280- _showSnackBar(
303+ Navigator.pushNamedAndRemoveUntil(
281304 context,
282- 'Something went wrong (${authException.message})',
305+ HomeScreen.id,
306+ (route) => false,
283307 );
284-
285- debugPrint(authException.message);
308+ },
309+ onLoginFailed: (authException) {
310+ showSnackBar('Something went wrong!');
311+ log(VerifyPhoneNumberScreen.id, error: authException.message);
286312 // handle error further if needed
287313 },
288314 builder: (context, controller) {
289315 return Scaffold(
290316 appBar: AppBar(
291317 leadingWidth: 0,
292318 leading: const SizedBox.shrink(),
293- title: const Text(" Verify Phone Number" ),
319+ title: const Text(' Verify Phone Number' ),
294320 actions: [
295321 if (controller.codeSent)
296322 TextButton(
297323 child: Text(
298324 controller.timerIsActive
299- ? "${controller.timerCount.inSeconds}s"
300- : "RESEND",
301- style: const TextStyle(
302- color: Colors.blue,
303- fontSize: 18,
304- ),
325+ ? '${controller.timerCount.inSeconds}s'
326+ : 'Resend',
327+ style: const TextStyle(color: Colors.blue, fontSize: 18),
305328 ),
306329 onPressed: controller.timerIsActive
307330 ? null
308- : () async => await controller.sendOTP(),
331+ : () async {
332+ log(VerifyPhoneNumberScreen.id, msg: 'Resend OTP');
333+ await controller.sendOTP();
334+ },
309335 ),
310336 const SizedBox(width: 5),
311337 ],
312338 ),
313339 body: controller.codeSent
314340 ? ListView(
315341 padding: const EdgeInsets.all(20),
342+ controller: scrollController,
316343 children: [
317344 Text(
318- "We've sent an SMS with a verification code to $phoneNumber",
319- style: const TextStyle(
320- fontSize: 25,
321- ),
345+ "We've sent an SMS with a verification code to ${widget.phoneNumber}",
346+ style: const TextStyle(fontSize: 25),
322347 ),
323348 const SizedBox(height: 10),
324349 const Divider(),
325- AnimatedContainer(
326- duration: const Duration(seconds: 1),
327- height: controller.timerIsActive ? null : 0,
328- child: Column(
350+ if (controller.timerIsActive)
351+ Column(
329352 children: const [
330- CircularProgressIndicator.adaptive (),
353+ CustomLoader (),
331354 SizedBox(height: 50),
332355 Text(
333- " Listening for OTP" ,
356+ ' Listening for OTP' ,
334357 textAlign: TextAlign.center,
335358 style: TextStyle(
336359 fontSize: 25,
337360 fontWeight: FontWeight.w600,
338361 ),
339362 ),
363+ SizedBox(height: 15),
340364 Divider(),
341- Text("OR" , textAlign: TextAlign.center),
365+ Text('OR' , textAlign: TextAlign.center),
342366 Divider(),
343367 ],
344368 ),
345- ),
369+ const SizedBox(height: 15 ),
346370 const Text(
347- " Enter OTP" ,
371+ ' Enter OTP' ,
348372 style: TextStyle(
349373 fontSize: 20,
350374 fontWeight: FontWeight.w600,
351375 ),
352376 ),
353- TextField(
354- maxLength: 6,
355- keyboardType: TextInputType.number,
356- onChanged: (String v) async {
357- _enteredOTP = v;
358- if (_enteredOTP?.length == 6) {
359- final isValidOTP = await controller.verifyOTP(
360- otp: _enteredOTP!,
361- );
362- // Incorrect OTP
363- if (!isValidOTP) {
364- _showSnackBar(
365- context,
366- "Please enter the correct OTP sent to $phoneNumber",
367- );
368- }
377+ const SizedBox(height: 15),
378+ PinInputField(
379+ length: 6,
380+ onFocusChange: (hasFocus) async {
381+ if (hasFocus) await _scrollToBottomOnKeyboardOpen();
382+ },
383+ onSubmit: (enteredOTP) async {
384+ final isValidOTP = await controller.verifyOTP(
385+ otp: enteredOTP,
386+ );
387+ // Incorrect OTP
388+ if (!isValidOTP) {
389+ showSnackBar('The entered OTP is invalid!');
369390 }
370391 },
371392 ),
@@ -375,11 +396,11 @@ class VerifyPhoneNumberScreen extends StatelessWidget {
375396 mainAxisAlignment: MainAxisAlignment.center,
376397 crossAxisAlignment: CrossAxisAlignment.center,
377398 children: const [
378- CircularProgressIndicator.adaptive (),
399+ CustomLoader (),
379400 SizedBox(height: 50),
380401 Center(
381402 child: Text(
382- " Sending OTP" ,
403+ ' Sending OTP' ,
383404 style: TextStyle(fontSize: 25),
384405 ),
385406 ),
@@ -391,7 +412,6 @@ class VerifyPhoneNumberScreen extends StatelessWidget {
391412 );
392413 }
393414}
394-
395415```
396416
397417See the [ ` example ` ] ( https://github.com/rithik-dev/firebase_phone_auth_handler/blob/master/example ) directory for a complete sample app.
0 commit comments