How to fix eternal loading when fetching data in flutter? - flutter

I am trying to make sign in in dart using cubit/bloc.
And when I try to change state of my cubit(auth) to Waiting => CircularProgressIndicator
Then my app is getting stuck when api returns statusCode == 400 and I call another emit to show dialog window
But if sign in is successful(statusCode == 200) then everything is ok, and I navigate to main_page.dart
How to fix this problem and transfer data reponse from api to widget(I want to send statusCode and message to widget)
My cubit file:
class AuthCubit extends Cubit<AuthState> {
AuthCubit() : super(AuthState(
email: "",
password: "",
));
final ApiService _apiService = ApiService();
void setEmail(String email) => emit(state.copyWith(email: email));
void setPassword(String password) => emit(state.copyWith(password: password));
Future signIn() async {
emit(WaitingSignInAuth(state.email, state.password));
try {
final data = await _apiService.signIn(
email: state.email ?? "",
password: state.password ?? ""
);
if (data['success']) {
print(data);
emit(const SuccessAutentification());
}
} on DioError catch (ex) {
print(ex.toString());
//I want to transfer to ErrorSignInAuth ex.response!.data['message'] but I don't know how
emit(ErrorSignInAuth(state.email, state.password));
} catch (e) {
print(e);
}
}
}
This is my state file:
class AuthState extends Equatable {
final String? email;
final String? password;
const AuthState({
this.email,
this.password,
});
AuthState copyWith({
String? email,
String? password,
}) {
return AuthState(
email: email ?? this.email,
password: password ?? this.password,
);
}
#override
List<Object?> get props =>
[email, password];
}
class SuccessAutentification extends AuthState {
const SuccessAutentification() : super();
}
class WaitingSignInAuth extends AuthState {
const WaitingSignInAuth(email, password) : super(email: email, password: password);
}
class ErrorSignInAuth extends AuthState {
const ErrorSignInAuth(email, password) : super(email: email, password: password);
}
And this is the widget where I use this cubit:
#override
Widget build(BuildContext context) {
return BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) {
if (state is WaitingSignInAuth) {
showDialog(
context: context,
builder: (context) => Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.black.withOpacity(0.6),
child: const Center(
child: CircularProgressIndicator(
strokeWidth: 1,
color: Colors.black,
backgroundColor: Colors.white,
),
),
));
}
if (state is SuccessAutentification) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const MainWidget(),
),
);
}
if (state is ErrorAuthentification) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Bad request"),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text("Error") // I want to show here error message
],
),
),
actions: <Widget>[
TextButton(
child: const Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
}
);
}
},
builder: (context, state) {
return Scaffold(
LoginButton(
color: _isValid ? AppColors.white : AppColors.whiteA3A3A3,
text: "Login in",
textColor: Colors.black,
isButtonDisabled: _isValid ? false : true,
onPressed: () {
if (_key.currentState!.validate()) {
BlocProvider.of<AuthCubit>(context).setEmail(_email.text.trim());
BlocProvider.of<AuthCubit>(context).setPassword(_password.text.trim());
BlocProvider.of<AuthCubit>(context).signIn();
_key.currentState!.save();
}
},
)
);
},
);
}

In signIn() function, after you emit WaitingSignInAuth state, in some cases you are not emitting any other state, so your page is always loading.
Future signIn() async {
emit(WaitingSignInAuth(state.email, state.password));
try {
final data = await _apiService.signIn(
email: state.email ?? "",
password: state.password ?? ""
);
if (data['success']) {
print(data);
emit(const SuccessAutentification());
} else {
// emit state
emit(ErrorSignInAuth(state.email, state.password));
}
} on DioError catch (ex) {
print(ex.toString());
emit(ErrorSignInAuth(state.email, state.password));
} catch (e) {
print(e);
// emit state
emit(ErrorSignInAuth(state.email, state.password));
}
}

Related

Debugging authStateChanges() stream leading to deauthentication due to async call not finishing on time

The previous developer has implemented an authentication mechanism that's very hit or miss when it comes to successfully authenticating users. I've been trying to debug the issues, but this last one persists.
The logic for the authentication is the following:
Users opens the app and is navigated to the splash screen
The splash screen's cubit makes an async request to get an AuthModel that contains the Firebase user's UUID and if successful, emits an "already logged in" state
The splash screen then reacts to the state and navigates the user to the main screen
The main screen seems to listen for event emissions by the AppBloc & MainBloc in order to determine if the user can be shown the actual content.
The issue is that although steps (1), (2) & (3) seem to initially work, after running the app in debug mode a lot, the AppBloc's on<AppEventAuthModelChanged$>(_onUserChanged); runs after receiving a new AuthModel emission and inside the _onUserChanged function, we land on the return emit(const AppState.unauthenticated()) statement. After observing the debugger, this is caused because the _authRepository.token != null expression is false at that point (but becomes true later).
This token is retrieved via the await _connectToServer(firebaseUser: firebaseUser); call inside the Auth Repository Impl's_ensureUser() function that runs based on the authStateChanges() stream via the getter.
Now, I have observed that the _ensureUser() is run multiple times and always seems to emit a valid Firebase user. However, the call to the _connectToServer() seems to take too long to finish (in order to initialize the token field), and thus the AppBloc's logic in the _onUserChanged call fails. What would be a valid approach so that the call has finished in order for us to be sure that the token was either retrieved or that something actually went wrong?
Is the code unsalvageable?
Splash_component.dart for the splash_screen.dart:
class SplashComponent extends StatefulWidget {
const SplashComponent({Key? key}) : super(key: key);
#override
State<SplashComponent> createState() => _SplashComponentState();
}
class _SplashComponentState extends State<SplashComponent> {
SplashCubit get _cubit => context.read<SplashCubit>();
bool _isTextVisible = false;
#override
void initState() {
super.initState();
_cubit.init();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.red,
body: BlocConsumer<SplashCubit, SplashState>(
listener: (BuildContext context, SplashState state) {
state.maybeWhen(
newUser: () async {
setState(() => _isTextVisible = true);
//await Future<void>.delayed(const Duration(seconds: 1));
NavigatorUtils.replaceToAuthScreen(context);
},
alreadyLoggedIn: () async {
setState(() => _isTextVisible = true);
//await Future<void>.delayed(const Duration(seconds: 1));
NavigatorUtils.replaceToMainScreen(context);
},
orElse: () {},
);
},
builder: (BuildContext context, SplashState state) {
return state.maybeWhen(
orElse: () {
return Padding(
padding: const EdgeInsets.all(40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset('assets/images/blob_logo_white_splash.png'),
const SizedBox(height: 24),
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: _isTextVisible ? 1.0 : 0.0,
child: Text(
'Carry your art with you',
style: montserratRegular22.copyWith(
color: AppColors.white),
),
),
],
),
);
},
);
},
),
);
}
}
Splash_cubit.dart:
class SplashCubit extends Cubit<SplashState> {
SplashCubit({required AuthRepository authRepository})
: _authRepository = authRepository,
super(const SplashState.loading());
final AuthRepository _authRepository;
Future<void> init() async {
try {
final AuthModel? authModel = await _authRepository.authModel.first;
if (authModel != null) {
return emit(const SplashState.alreadyLoggedIn());
}
return emit(const SplashState.newUser());
} catch (_) {
return emit(const SplashState.newUser());
}
}
}
Main_component.dart for the main_screen.dart:
const double _iconSize = 24;
class MainComponent extends StatelessWidget {
const MainComponent({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<AppBloc, AppState>(
listenWhen: (AppState previous, AppState current) {
return previous is AppStateAuthenticated$ &&
current is AppStateUnauthenticated$;
},
listener: (BuildContext context, AppState state) {
state.maybeWhen(
unauthenticated: () => NavigatorUtils.replaceToAuthScreen(context),
orElse: () {},
);
},
child: BlocBuilder<MainCubit, MainState>(
builder: (BuildContext context, MainState state) {
final MainCubit cubit = context.read<MainCubit>();
final AppState appState = context.watch<AppBloc>().state;
final TabType tab = state.tab;
return Scaffold(
body: appState.maybeWhen(
authenticated: (_) {
return SafeArea(
child: IndexedStack(
index: tab.index,
children: const <Widget>[
AlbumScreen(),
CameraScreen(),
ExploreScreen(),
],
),
);
},
unauthenticated: () {
NavigatorUtils.replaceToAuthScreen(context);
return const SizedBox.shrink();
},
orElse: () => const SizedBox.shrink(),
),
bottomNavigationBar: BottomNavigationBar(
onTap: (int index) => cubit.changeTab(TabType.values[index]),
currentIndex: cubit.state.tab.index,
elevation: 0,
selectedItemColor: AppColors.red,
selectedFontSize: 12,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Image.asset(TabType.album.asset,
color: AppColors.grey,
height: _iconSize,
width: _iconSize),
activeIcon: Image.asset(TabType.album.asset,
color: AppColors.red,
height: _iconSize,
width: _iconSize),
label: 'ALBUM',
),
BottomNavigationBarItem(
icon: Image.asset(
TabType.camera.asset,
color: AppColors.grey,
height: _iconSize,
width: _iconSize,
),
activeIcon: Image.asset(
TabType.camera.asset,
color: AppColors.red,
height: _iconSize,
width: _iconSize,
),
label: 'CAMERA',
),
BottomNavigationBarItem(
icon: Image.asset(
TabType.explore.asset,
color: AppColors.grey,
height: _iconSize,
width: _iconSize,
),
activeIcon: Image.asset(
TabType.explore.asset,
color: AppColors.red,
height: _iconSize,
width: _iconSize,
),
label: 'EXPLORE',
),
],
),
);
},
),
);
}
}
app_bloc.dart:
class AppBloc extends Bloc<AppEvent, AppState> {
AppBloc({
required AuthRepository authRepository,
required UserRepository userRepository,
required ArtworkRepository artworkRepository,
required ArtistRepository artistRepository,
required GalleryRepository galleryRepository,
required VenueRepository venueRepository,
}) : _authRepository = authRepository,
_userRepository = userRepository,
_artworkRepository = artworkRepository,
_artistRepository = artistRepository,
_galleryRepository = galleryRepository,
_venueRepository = venueRepository,
super(const AppState.initial()) {
on<AppEventAuthModelChanged$>(_onUserChanged);
on<AppEventLogout$>(_onLogout);
_authModelSubscription = _authRepository.authModel.listen(
(AuthModel? authModel) => add(AppEvent.authModelChanged(authModel)),
);
}
final AuthRepository _authRepository;
final UserRepository _userRepository;
final ArtworkRepository _artworkRepository;
final ArtistRepository _artistRepository;
final GalleryRepository _galleryRepository;
final VenueRepository _venueRepository;
late StreamSubscription<AuthModel?> _authModelSubscription;
Future<void> _onUserChanged(
AppEventAuthModelChanged$ event, Emitter<AppState> emit) async {
final AuthModel? authModel = event.authModel;
if (authModel != null &&
state is! AppStateUnauthenticated$ &&
_authRepository.token != null) {
final String token = _authRepository.token!;
_userRepository.token = token;
_artworkRepository.token = token;
_artistRepository.token = token;
_galleryRepository.token = token;
_venueRepository.token = token;
await _artworkRepository.getSavedArtworks();
final User user = await _userRepository.getUserDetails();
return emit(AppState.authenticated(user: user));
} else {
_authRepository.token = null;
_artworkRepository.token = null;
_artistRepository.token = null;
return emit(const AppState.unauthenticated());
}
}
Future<void> _onLogout(AppEventLogout$ event, Emitter<AppState> emit) async {
unawaited(_authRepository.logOut());
emit(const AppState.unauthenticated());
}
#override
Future<void> close() {
_authModelSubscription.cancel();
return super.close();
}
}
auth_repository_impl.dart:
class AuthRepositoryImpl implements AuthRepository {
AuthRepositoryImpl({required DioClient dioClient}) : _client = dioClient;
final DioClient _client;
final auth.FirebaseAuth _auth = auth.FirebaseAuth.instance;
#override
String? token;
bool isFetchingToken = false;
#override
Stream<AuthModel?> get authModel {
return MergeStream<auth.User?>(
<Stream<auth.User?>>[
_auth.authStateChanges(),
],
)
.startWith(_auth.currentUser) //
.switchMap<AuthModel?>(_ensureUser)
.share()
.distinct();
}
Stream<AuthModel?> _ensureUser(auth.User? firebaseUser) async* {
if (firebaseUser == null) {
yield* Stream<AuthModel?>.value(null);
return;
}
if (token == null && !isFetchingToken) {
await _connectToServer(firebaseUser: firebaseUser);
}
yield* Stream<AuthModel?>.value(AuthModel(uid: firebaseUser.uid));
}
Future<void> _connectToServer(
{auth.UserCredential? userCredential, auth.User? firebaseUser}) async {
try {
String? firebaseToken;
isFetchingToken = true;
if (userCredential != null) {
firebaseToken = await userCredential.user?.getIdToken();
} else {
firebaseToken = await firebaseUser?.getIdToken();
}
final String email =
userCredential?.user?.email ?? firebaseUser?.email ?? '';
final String baseUrl = getIt<AppRepository>().env == Env.staging
? stagingUrl
: productionUrl;
final ApiResponse response = await _client.httpCall(
baseUrl: baseUrl,
path: '/mobile/login',
httpMethod: HttpMethod.POST,
queryParameters: <String, dynamic>{
'email': email,
'token': firebaseToken,
},
);
final Map<String, dynamic> data = response.data;
token = data['message'];
isFetchingToken = false;
} on DioError catch (ex) {
if (ex.type == DioErrorType.connectTimeout) {
throw Exception('Connection Timeout Exception');
}
throw Exception(ex.message);
}
}
#override
Future<void> logOut() async => await _auth.signOut();
#override
Future<void> registerWithEmailAndPassword(
{required String email, required String password}) async {
final auth.UserCredential userCredential =
await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
await _connectToServer(userCredential: userCredential);
}
#override
Future<void> loginWithApple() async {
final String rawNonce = NonceGenerator.generateNonce();
final String nonce = NonceGenerator.sha256ofString(rawNonce);
final AuthorizationCredentialAppleID appleCredential =
await SignInWithApple.getAppleIDCredential(
scopes: <AppleIDAuthorizationScopes>[
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName
],
nonce: nonce,
);
final auth.OAuthCredential oauthCredential =
auth.OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
final auth.UserCredential userCredential =
await auth.FirebaseAuth.instance.signInWithCredential(oauthCredential);
await _connectToServer(userCredential: userCredential);
}
#override
Future<void> loginWithEmailAndPassword(
{required String email, required String password}) async {
try {
final auth.UserCredential userCredential =
await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
await _connectToServer(userCredential: userCredential);
log('z1z:: Server token $token');
} catch (e) {
rethrow;
}
}
#override
Future<void> resetPassword({required String email}) async =>
await _auth.sendPasswordResetEmail(email: email);
}

Flutter app login after a logout PostgresSQLException

I'm working on a Flutter app using Riverpod for the state management and Postgres for the database. I try to do a simple thing: display a message in the home screen with the nickname of the current user. When I logout and login to check if my feature works, my user is null and my console display a PostgresSQLException :
PostgreSQLSeverity.error : Attempting to reopen a closed connection. Create a instance instead.
Any idea where I made a mistake?
My user_repository:
PostgreSQLConnection connection = PostgreSQLConnection(
'10.0.2.2', 5432, DatabaseAccess.databaseName,
queryTimeoutInSeconds: 3600,
timeoutInSeconds: 3600,
username: DatabaseAccess.databaseUser,
password: DatabaseAccess.databasePassword);
Future<AppUser?> getCurrentUser() async {
try {
await connection.open();
final result = await connection.mappedResultsQuery(
'select * from public.user where email = #emailValue',
substitutionValues: {
'emailValue': email,
},
allowReuse: true,
timeoutInSeconds: 30,
);
final userFromDataBase = result[0]['user']!;
return AppUser(
email: userFromDataBase['email'],
nickname: userFromDataBase['nickname'],
role: userFromDataBase['role'],
firstname: userFromDataBase['firstname'],
lastname: userFromDataBase['lastname'],
);
} on PostgreSQLException catch(e) {
print(ErrorHandler(message: e.toString()));
return null;
}
}
My screen:
class HomeScreen extends HookConsumerWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final currentUser = ref.watch(currentUserProvider);
return Scaffold(
body: currentUser.when(
data: (user) => _buildBody(context, user, ref),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => _errorBody(context, ref),
)
);
}
Widget _buildBody(BuildContext context, AppUser? user, WidgetRef ref) {
if(user == null) {
return _errorBody(context, ref);
} else {
return Center(child: Text(
'Welcome ${user.getNickname}',
style: const TextStyle(fontSize: 20),
));
}
}
Widget _errorBody(BuildContext context, WidgetRef ref) {
return const Center(child: Text(
"Error: No user found",
style: TextStyle(fontSize: 20, color: Colors.red),
));
}
}
I resolved this issue with the following code. It wasn't a postgres issue but a dark story about riverpod's providers.
My providers.dart :
final futureCurrentUserProvider = Provider<Future<AppUser?>>((ref) {
return UserRepository().getCurrentUser(ref.watch(emailChangeProvider));
});
final currentUserProvider = FutureProvider.autoDispose<AppUser?>((ref) => UserRepository().getCurrentUser(ref.watch(emailChangeProvider)));
final authChangeProvider = StreamProvider<User?>((ref) {
return ref.read(authRepositoryProvider).authUserChange;
});
final emailChangeProvider = Provider<String?>((ref) {
return ref.watch(authChangeProvider).value?.email;
});

_TypeError was thrown building NotesListView(dirty) : type 'Null' is not a subtype of type 'String'

I don't know where this error is coming from
enter image description here
the debug console says the error is in the returning line and the return line return just returning a widget but the error is about string is null i don't from where this error is coming
this is notes_view.dart file
class NotesView extends StatefulWidget {
const NotesView({super.key});
#override
State<NotesView> createState() => _NotesViewState();
}
class _NotesViewState extends State<NotesView> {
late final FirebaseCloudStorage _notesService;
String get userId => AuthService.firebase().currentUser!.id;
#override
void initState() {
_notesService = FirebaseCloudStorage();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue[100],
appBar: AppBar(
title: const Text("Your Notes"),
actions: [
IconButton(
onPressed: () {
Navigator.of(context).pushNamed(createOrUpdateNoteRoute);
},
icon: const Icon(Icons.add),
),
PopupMenuButton<MenuActions>(
onSelected: (value) async {
switch (value) {
case MenuActions.logout:
final shouldLogout = await showLogoutDialog(context);
if (shouldLogout) {
await AuthService.firebase().logOut();
Navigator.of(context)
.pushNamedAndRemoveUntil(loginRoute, (_) => false);
}
break;
}
},
itemBuilder: (context) {
return const [
PopupMenuItem<MenuActions>(
value: MenuActions.logout, child: Text("Log out")),
];
},
)
],
),
body: StreamBuilder(
stream: _notesService.allNotes(ownerUserId: userId),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
if (snapshot.hasData) {
final allNotes = snapshot.data as Iterable<CloudNote>;
return NotesListView(
notes: allNotes,
onDeleteNote: (note) async {
await _notesService.deleteNote(
documentId: note.documentId);
print(note.documentId);
},
onTap: (note) {
Navigator.of(context).pushNamed(
createOrUpdateNoteRoute,
arguments: note,
);
},
);
} else {
return const CircularProgressIndicator();
}
default:
return const CircularProgressIndicator();
}
},
));
}
}
this is notes_list_view.dart file
typedef NoteCallback = void Function(CloudNote note);
class NotesListView extends StatelessWidget {
final Iterable<CloudNote> notes;
final NoteCallback onDeleteNote;
final NoteCallback onTap;
const NotesListView({
Key? key,
required this.notes,
required this.onDeleteNote,
required this.onTap,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: notes.length,
itemBuilder: (context, index) {
final note = notes.elementAt(index);
return ListTile(
onTap: () {
onTap(note);
},
textColor: Colors.black87,
title: Text(
note.text,
maxLines: 1,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
trailing: IconButton(
icon: Icon(
Icons.delete,
color: Colors.red[200],
),
onPressed: () async {
final shouldDelete = await deleteDialog(context);
if (shouldDelete) {
onDeleteNote(note);
}
},
),
);
},
);
}
}
this is the firebase_cloud_storage.dart file
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:mynotes/services/cloud/cloud_note.dart';
import 'package:mynotes/services/cloud/cloud_storage_constants.dart';
import 'package:mynotes/services/cloud/cloud_storage_exceptions.dart';
class FirebaseCloudStorage {
final notes = FirebaseFirestore.instance.collection('notes');
Future<void> deleteNote({required String documentId}) async {
try {
await notes.doc(documentId).delete();
} catch (e) {
throw CouldNotDeleteNoteException();
}
}
Future<void> updateNote({
required String documentId,
required String text,
}) async {
try {
await notes.doc(documentId).update({textFieldName: text});
} catch (_) {
throw CouldNotUpdateNotesException();
}
}
Stream<Iterable<CloudNote>> allNotes({required String ownerUserId}) {
return notes.snapshots().map((event) => event.docs
.map((doc) => CloudNote.fromSnapshot(doc))
.where((note) => note.ownerUserId == ownerUserId));
}
Future<Iterable<CloudNote>> getNotes({required String ownerUserId}) async {
try {
return await notes
.where(ownerUserIdFieldName, isEqualTo: ownerUserId)
.get()
.then(
(value) => value.docs.map((doc) => CloudNote.fromSnapshot(doc)),
);
} catch (e) {
throw CouldNotGetAllNotesException();
}
}
Future<CloudNote> createNewNote({required String ownerUserId}) async {
final document = await notes.add({
ownerUserIdFieldName: ownerUserId,
textFieldName: '',
});
final fetchedNote = await document.get();
return CloudNote(
documentId: fetchedNote.id,
ownerUserId: ownerUserId,
text: '',
);
}
static final FirebaseCloudStorage _shared =
FirebaseCloudStorage._sharedInstance();
FirebaseCloudStorage._sharedInstance();
factory FirebaseCloudStorage() => _shared;
}
#immutable
class CloudNote {
final String documentId;
final String ownerUserId;
final String text;
const CloudNote({
required this.documentId,
required this.ownerUserId,
required this.text,
});
CloudNote.fromSnapshot(QueryDocumentSnapshot<Map<String, dynamic>> snapshot)
: documentId = snapshot.id,
ownerUserId = snapshot.data()[ownerUserIdFieldName],
text = snapshot.data()[textFieldName] as String;
}
I will like to accept null data while reading map, try
text = snapshot.data()[textFieldName] ?? "got null value";
CloudNote.fromSnapshot(QueryDocumentSnapshot<Map<String, dynamic>> snapshot)
: documentId = snapshot.id,
ownerUserId = snapshot.data()[ownerUserIdFieldName] ?? "default value",
text = snapshot.data()[textFieldName] ?? "Default value";

BlocProvider.of() called with a context that does not contain a Bloc of type PhoneAuthenticationBloc. Flutter

I create blocs in a MultiBlocProvider, its child is a BlocBuilder that returns a MultiBlocListener but when sending an event
BlocProvider.of<PhoneAuthenticationBloc>(context).add(VerifyPhoneNumberEvent(phoneNumber: controller.text.replaceAll(' ', '')));
I get the BlocProvider.of() called with a context that does not contain a Bloc of type PhoneAuthenticationBloc, while other blocs work fine.
Can you spot what's wrong with PhoneAuthenticationBloc()?
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<PhoneAuthenticationBloc>(
create: (context) => PhoneAuthenticationBloc(userRepository: UserRepository()),
),
// BlocProvider<AuthenticationBloc>(
// create: (context) => AuthenticationBloc(userRepository: UserRepository()),
// lazy: false,
// ),
// BlocProvider<UserBloc>(
// create: (context) => UserBloc(),
// lazy: false,
// ),
BlocProvider<BookingBloc>(
create: (context) => BookingBloc(user: widget.user),
),
BlocProvider<OrderBloc>(
create: (context) => OrderBloc(user: widget.user),
),
BlocProvider<PaymentBloc>(
create: (context) => PaymentBloc(user: widget.user),
lazy: false,
),
BlocProvider<CartBloc>(
create: (context) => CartBloc()..add(LoadCart()),
),
],
child: BlocBuilder<PaymentBloc, PaymentState>(
builder: (context, state) {
if (state is InitialStatePayment) {
return MultiBlocListener(
listeners: [
BlocListener<PhoneAuthenticationBloc, AuthenticationState>(
listener: (BuildContext context, AuthenticationState state){
...
FlatButton.icon(
color: Colors.orange,
onPressed: () {
print('pay pressed');
print(
'bookingStart is ${widget.bookingStart}, selected shop is ${widget.selectedShop}');
if (isVerified == true){
...
}
else{
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return SingleChildScrollView(
child: ValidatePhoneDialog(
controller: controller,
onPressed: (){
if (controller.text.length >= 9){
Navigator.pop(context);
showDialog(
context:context,
barrierDismissible: false,
builder: (BuildContext context){
return VerifyingDialog();
}
);
BlocProvider.of<PhoneAuthenticationBloc>(context).add(VerifyPhoneNumberEvent(phoneNumber: controller.text.replaceAll(' ', '')));
} else {
scaffoldKey.currentState.showSnackBar(SnackBar(
backgroundColor: Colors.redAccent,
content: Text(
AppLocalizations.instance
.text('Wrong number'),
style: TextStyle(color: Colors.white))));
}
}
),
);
}
);
}
},
icon: Icon(
Icons.payment,
color: Colors.white,
),
label: Text(
AppLocalizations.instance.text('Pay'),
style: TextStyle(
color: Colors.white, fontSize: 20),
)),
class PhoneAuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
final UserRepository _userRepository;
PhoneAuthenticationBloc({#required UserRepository userRepository})
: assert(userRepository != null),
_userRepository = userRepository;
String verificationId = "";
#override
AuthenticationState get initialState => Uninitialized();
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event) async* {
// phone verification
if (event is VerifyPhoneNumberEvent) {
print('VerifyPhoneNumberEvent received');
yield* _mapVerifyPhoneNumberToState(event);
} else if (event is PhoneCodeSentEvent) {
print('PhoneCodeSentEvent received');
yield OtpSentState();
} else if (event is VerificationCompletedEvent) {
print('VerificationCompletedEvent received');
yield VerificationCompleteState(firebaseUser: event.firebaseUser, isVerified: event.isVerified);
} else if (event is VerificationExceptionEvent) {
print('VerificationExceptionEvent received');
yield VerificationExceptionState(message: event.message);
} else if (event is VerifySmsCodeEvent) {
print('VerifySmsCodeEvent received');
yield VerifyingState();
try {
AuthResult result =
await _userRepository.verifyAndLinkAuthCredentials(verificationId: verificationId, smsCode: event.smsCode);
if (result.user != null) {
yield VerificationCompleteState(firebaseUser: result.user, isVerified: true);
} else {
yield OtpExceptionState(message: "Invalid otp!",verificationId: verificationId);
}
} catch (e) {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
print(e);
}
} else if ( event is PhoneCodeAutoRetrievalTimeoutEvent){
yield PhoneCodeAutoRetrievalTimeoutState(verificationId: event.verificationId);
}
if(event is SendVerificationCodeEvent) {
yield*_mapVerificationCodeToState(event);
}
}
Stream<AuthenticationState> _mapVerifyPhoneNumberToState(VerifyPhoneNumberEvent event) async* {
print('_mapVerifyPhoneNumberToState V2 started');
yield VerifyingState();
final phoneVerificationCompleted = (AuthCredential authCredential) {
print('_mapVerifyPhoneNumberToState PhoneVerificationCompleted');
// _userRepository.getUser();
_userRepository.getCurrentUser().catchError((onError) {
print(onError);
}).then((user) {
add(VerificationCompletedEvent(firebaseUser: user, isVerified: true));
});
};
final phoneVerificationFailed = (AuthException authException) {
print('_mapVerifyPhoneNumberToState PhoneVerificationFailed');
print(authException.message);
add(VerificationExceptionEvent(onError.toString()));
};
final phoneCodeSent = (String verificationId, [int forceResent]) {
print('_mapVerifyPhoneNumberToState PhoneCodeSent');
this.verificationId = verificationId;
add(PhoneCodeSentEvent());
};
final phoneCodeAutoRetrievalTimeout = (String verificationId) {
// after this print Bloc error is Bad state: Cannot add new events after calling close
print('_mapVerifyPhoneNumberToState PhoneCodeAutoRetrievalTimeout');
this.verificationId = verificationId;
add(PhoneCodeAutoRetrievalTimeoutEvent(verificationId: verificationId));
};
await _userRepository.verifyPhone(
phoneNumber: event.phoneNumber,
timeOut: Duration(seconds: 0),
phoneVerificationFailed: phoneVerificationFailed,
phoneVerificationCompleted: phoneVerificationCompleted,
phoneCodeSent: phoneCodeSent,
autoRetrievalTimeout: phoneCodeAutoRetrievalTimeout);
}
Stream<AuthenticationState> _mapVerificationCodeToState(SendVerificationCodeEvent event) async* {
print('_mapVerificationCodeToState started');
yield VerifyingState();
try {
AuthResult result =
await _userRepository.verifyAndLinkAuthCredentials(verificationId: verificationId, smsCode: event.smsCode);
if (result.user != null) {
yield VerificationCompleteState(firebaseUser: result.user, isVerified: true);
} else {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
}
} catch (e) {
yield OtpExceptionState(message: "Invalid otp!", verificationId: verificationId);
print(e);
}
}
}
You are using the wrong context when adding the event.
When showing the dialog the widget will be placed in an overlay which is above the bloc provider, so by using the context of the dialog you cannot find the bloc since there is no provider above it.
To fix this name the context of the dialog something else (ie. dialogContext) such that when doing BlocProvider.of(context) the context refers to the context of the widget showing the dialog instead of the context of the dialog itself.

cannot navigate form login screen to bottom_tab_screen with provider

I'm trying to navigate from login screen to the bottom tab screen but nothing happen and now i have no error
it is the main
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: UserProvider()),
ChangeNotifierProvider.value(value: AppProvider()),
],
child:MaterialApp(
key: key,
title: 'Voyager',
debugShowCheckedModeBanner: false,
theme: AppTheme.getTheme(),
routes: routes,
),
);
}
my dialog which has two cases if success or fail to login or sign up
import 'package:flutter/material.dart';
class Dialogs {
static showErrorDialog(BuildContext context,
{#required String message, #required int code}) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Ok'),
)
],
title: Text('error $code'),
content: Text(message),
backgroundColor: Colors.white,
);
},
);
}
}
my login method and it depend on user api provider
signIn() async {
var res = await userProvider.login(
_userNameController.text, _passwordController.text);
if (res is FailedRequest) {
Dialogs.showErrorDialog(widget._context , message: res.message, code: res.code);
print('results ${res.toString()}');
} else {
print("Signing in success");
Navigator.pushReplacement(
widget._context, MaterialPageRoute(builder: (context) => BottomTabScreen()));
}
userProvider.isLoading = false;
}
and the api provider which use in the login
Future<dynamic> login(String email, String password) async {
final Map<String, dynamic> body = {'email': email, 'password': password};
_isLoading = true;
notifyListeners();
print('Starting request');
http.Response response = await http.post(Environment.userLogin,
body: json.encode(body), headers: Environment.requestHeader);
print('Completed request');
print('user login response : ${response.body}');
Map<String, dynamic> res = json.decode(response.body);
var results;
if (res['code'] == 200) {
// login successful
_user = User.fromJson(res['message']);
results = true;
} else {
// login failed;
results =
FailedRequest(code: 400, message: res['error'], status: false);
}
_isLoading = false;
notifyListeners();
return results;
}
finally the failed request class if request not done
import 'package:flutter/foundation.dart';
class FailedRequest {
String message;
int code;
bool status;
FailedRequest({
#required this.message,
#required this.code,
#required this.status,
});
}
The Issue seems to be with the res['error'] can you verify that the field error actually exists and is not null.
At this block can you print the value of res['error']
else {
print(res['error']);
// login failed;
results =
FailedRequest(code: 400, message: res['error'], status: false);
}