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.
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;
});