Search code examples
flutterfirebase-authentication

Problem while performing phone authentication for my flutter app with Bloc


The following is my event which gets called when tapping on the send code button after entering the phone number where verifyOTPstate is not getting emitted:

 on<sendOtpEvent>((event,emit)async{
         await _auth.verifyPhoneNumber(
          phoneNumber:event.number,
          verificationCompleted:(PhoneAuthCredential credentials){},
          verificationFailed: (FirebaseAuthException e) {
            emit(ErrorState(error: e.message.toString()));
          },
          codeSent: (String verificationId, int? forceResendingToken) async{
              emit(verifyOTPState(verificationID: verificationId));
              
            },
          codeAutoRetrievalTimeout: (String verificationId) {
          });
    });

The error

   E/flutter ( 7268): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: 'package:bloc/src/emitter.dart': Failed assertion: line 114 pos 7: '!_isCompleted': 
E/flutter ( 7268): 
E/flutter ( 7268): emit was called after an event handler completed normally.
E/flutter ( 7268): This is usually due to an unawaited future in an event handler.
E/flutter ( 7268): Please make sure to await all asynchronous operations with event handlers
E/flutter ( 7268): and use emit.isDone after asynchronous operations before calling emit() to
E/flutter ( 7268): ensure the event handler has not completed.
E/flutter ( 7268): 
E/flutter ( 7268):   **BAD**
E/flutter ( 7268):   on<Event>((event, emit) {
E/flutter ( 7268):     future.whenComplete(() => emit(...));
E/flutter ( 7268):   });
E/flutter ( 7268): 
E/flutter ( 7268):   **GOOD**
E/flutter ( 7268):   on<Event>((event, emit) async {
E/flutter ( 7268):     await future.whenComplete(() => emit(...));
E/flutter ( 7268):   });

This is the Login Screen Page on which the send code button will call the send OTP event:

TextEditingController _phoneController=TextEditingController();
  final _formKey = GlobalKey<FormState>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: BlocBuilder<whatsAppBloc,whatsAppStates>(
        builder: (context,state){
            if(state is verifyOTPState){
              Navigator.pushReplacement(context,MaterialPageRoute(builder:(context)=>VerifyOTP(verificationID: state.verificationID, CurrentUserPhoneNumber:_phoneController.text)));
            }
            if(state is ErrorState)
              {
                ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:Text(state.error.toString())));
              }
            return  SingleChildScrollView(
              child: SizedBox(
                height: MediaQuery.sizeOf(context).height,
                width: MediaQuery.sizeOf(context).width,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Image.asset(
                      "assets/Images/whatsapp.png",
                      scale: 4,
                    ),
                    RichText(
                        textAlign: TextAlign.center,
                        text: const TextSpan(children: [
                          TextSpan(
                              style: TextStyle(
                                  height: 2.5,
                                  fontFamily: "Helvetica",
                                  fontWeight: FontWeight.w600,
                                  color: Color.fromRGBO(89, 176, 114, 1),
                                  fontSize: 33),
                              text: "WhatsApp\n"),
                          TextSpan(
                              style: TextStyle(
                                  height: 1.5,
                                  fontFamily: "Helvetica",
                                  color: Colors.black,
                                  fontSize: 14,
                                  fontWeight: FontWeight.normal),
                              text: "Enter your Phone Number \nto continue\n"),
                        ])),
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Row(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Expanded(
                            flex: 1,
                            child: Container(
                              height: 55,
                              alignment: Alignment.center,
                              decoration: BoxDecoration(
                                  borderRadius: BorderRadius.circular(10),
                                  border: Border.all(color: Colors.grey)
                              ),
                              child: Text("+91",style: TextStyle(fontSize: 16,fontWeight:FontWeight.normal,fontFamily: "Helvetica"),),
                            ),
                          ),
                          SizedBox(width: 5,),
                          Expanded(
                            flex: 7,
                            child: Form(
                              key: _formKey,
                              child: TextFormField(
                                controller: _phoneController,
                                validator: (value) {
                                  if (value == null || value.length < 10) {
                                    return "Please enter a valid phone number";
                                  } else {
                                    return null;
                                  }
                                },
                                textAlign: TextAlign.start,
                                cursorColor: Colors.black,
                                style: const TextStyle(
                                    fontWeight: FontWeight.normal,
                                    fontFamily: "Helvetica",
                                    fontSize: 16),
                                decoration: InputDecoration(
                                  focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(10),borderSide: BorderSide(color: Colors.green)),
                                  border: OutlineInputBorder(borderRadius: BorderRadius.circular(10),borderSide: BorderSide(color: Colors.black)),
                                ),
                                keyboardType: TextInputType.phone,
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                    ElevatedButton(
                        onPressed: () async{
                          if (_formKey.currentState!.validate() == true){
                             _formKey.currentState?.save();
                             BlocProvider.of<whatsAppBloc>(context).add(sendOtpEvent(context:context,number:"+91${_phoneController.text}"));
                          }
                        },
                        style: ElevatedButton.styleFrom(
                            elevation: 0,
                            shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(12)),
                            backgroundColor: Color.fromRGBO(32, 152, 34, 1),
                            foregroundColor: Colors.white),
                        child: const Text(
                          "Send Code",
                          style: TextStyle(
                              fontFamily: "Helvetica",
                              fontSize: 15,
                              fontWeight: FontWeight.bold),
                        ))
                  ],
                ),
              ),
            );
       },
      ),
    );
  }

The following is my event for calling Firebase phone auth:

class sendOtpEvent extends whatsAppEvents{
  BuildContext context;
  String number;
  sendOtpEvent({required this.number,required this.context});
}

Following are my states which will get called by my events:

class ErrorState extends whatsAppStates{
  String error;
  ErrorState({required this.error});
}
class verifyOTPState extends whatsAppStates{
  String verificationID;

  verifyOTPState({required this.verificationID});
}

I have tried async with the codeSent body but it does not work please provide a solution to this error.


Solution

  • The problem is because, the emit function is called inside the codeSent callback, which runs asynchronously.

    To resolve this issue, you can use a Completer to keep the event handler alive until the async operations are done:

    on<sendOtpEvent>((event, emit) async {
        final Completer<void> completer = Completer<void>();
    
        await _auth.verifyPhoneNumber(
          phoneNumber: event.number,
          verificationCompleted: (PhoneAuthCredential credentials) { },
          verificationFailed: (FirebaseAuthException e) {
            emit(ErrorState(error: e.message.toString()));
            completer.complete();
          },
          codeSent: (String verificationId, int? forceResendingToken) {
            emit(verifyOTPState(verificationID: verificationId));
            completer.complete();
          },
          codeAutoRetrievalTimeout: (String verificationId) { },
        );
    
        await completer.future;
    });