state is always null in flutter_bloc - flutter

I am facing this weird issue, where though I have set the state, but the value when accessed in init state is showing null !!
user_cubit.dart
UserCubit() : super(UserInitialState()) {
emit(UserMainLoadingState());
_firestore
.collection("users")
.doc(_currentUser?.uid)
.snapshots()
.listen((event) {
event.exists
? {
emit(UserExists(
userModel: UserModel.fromjson(event.data()!, event.id)))
}
: {print("There is no such user"), emit(UserNotExists())};
});
}
user_state.dart
class UserState extends Equatable {
final UserModel? userModel;
const UserState({this.userModel});
#override
List<Object?> get props => [userModel];
}
class UserExists extends UserState {
UserExists({required UserModel userModel}) : super(userModel: userModel) { // Here the state is received
print("I am inside the UserExists state and the user is :$userModel");
}
}
myWidget.dart
#override
void initState() {
_userState = const UserState();
print("I am inside the initState The value of userstate is ${_userState.userModel}"); // This prints null , though i have set the state and console logs the state and it is confirmed that state exists why isn't this not null
if (_userState.userModel != null) {
print("user is ${_userState.userModel.toString()}");
}
super.initState();
}
Console log:
I/flutter ( 5029): I am inside the UserExists state and the user is :avatar boy
I/flutter ( 5029): fullName krrrt
I/flutter ( 5029): dob 19/1/2022
I/flutter ( 5029): email rd#xddf.co
I/flutter ( 5029): phone 12222222255
I/flutter ( 5029): I am inside the initState The value of userstate is null
Though the userState's userModel has value, why can't i access that in the `initState.
My tries :
I have used BlocListener, still the state remains null, I'm hell confused.
body: BlocListener<UserCubit, UserState>(
listener: (context, state) {
print("I am inside the listener"); // this line is never printed
if (state.userModel != null) {
print("State is ${state.runtimeType}"); // But if the state has some value, why isn't this being printed !!!??
}
},
child: <My widget>

It seems that the issue is that you are initializing the _userState variable in the initState method with const UserState(), which sets the userModel value to null. This is happening before the UserExists state is emitted from the UserCubit, which sets the userModel value to a valid UserModel object.
You should instead initialize _userState with the current state of the UserCubit by calling the state getter.
#override
void initState() {
_userCubit = context.bloc<UserCubit>();
_userState = _userCubit.state;
print("I am inside the initState The value of userstate is ${_userState.userModel}");
if (_userState.userModel != null) {
print("user is ${_userState.userModel.toString()}");
}
super.initState();
}
It would also be best practice to unsubscribe the listener after the widget is disposed, in the dispose method of your stateful widget.
#override
void dispose() {
_userCubit.close();
super.dispose();
}
Note that the above code assumes that you are using the bloc package to manage the state of your app.

Related

How to access the state outside BlocConsumer and BlocCubit?

I am facing this weird issue, where though I have set the state, but the value when accessed in init state is showing null !!
user_cubit.dart
UserCubit() : super(UserInitialState()) {
emit(UserMainLoadingState());
_firestore
.collection("users")
.doc(_currentUser?.uid)
.snapshots()
.listen((event) {
event.exists
? {
emit(UserExists(
userModel: UserModel.fromjson(event.data()!, event.id)))
}
: {print("There is no such user"), emit(UserNotExists())};
});
}
user_state.dart
class UserState extends Equatable {
final UserModel? userModel;
const UserState({this.userModel});
#override
List<Object?> get props => [userModel];
}
class UserExists extends UserState {
UserExists({required UserModel userModel}) : super(userModel: userModel) {
print("I am inside the UserExists state and the user is :$userModel");
}
}
myWidget.dart
#override
void initState() {
_userState = const UserState();
print("I am inside the initState The value of userstate is ${_userState.userModel}"); // This prints null , but why ?
if (_userState.userModel != null) {
print("user is ${_userState.userModel.toString()}");
}
super.initState();
}
Console log:
I/flutter ( 5029): I am inside the UserExists state and the user is :avatar boy
I/flutter ( 5029): fullName krrrt
I/flutter ( 5029): dob 19/1/2022
I/flutter ( 5029): email rd#xddf.co
I/flutter ( 5029): phone 12222222255
I/flutter ( 5029): I am inside the initState The value of userstate is null
Though the userState's userModel has value, why can't i access that in the initState.
// Ignore this, this is for stackoverflow.
// Ignore this, this is for stackoverflow.
// Ignore this, this is for stackoverflow.
// Ignore this, this is for stackoverflow.
// Ignore this, this is for stackoverflow.
To access the state of a BLoC or Cubit in Flutter, you can use the context.read<Bloc>(). This method returns a Bloc instance that you can use to access the current state.
initState() {
super.initState();
_userState = const context.read<UserCubit>().state();
print("I am inside the initState The value of userstate is ${_userState.userModel}"); // This prints null , but why ?
if (_userState.userModel != null) {
print("user is ${_userState.userModel.toString()}");
}
}

flutter_login and flutter_bloc navigation after authentication: BlocListener not listening to state change

I am trying to combine this with bloc, using this design pattern from the docs.
After the state has been instantiated, BlocListener stops listening to the authentication bloc and I am kind of forced to use the login form's onSubmitAnimationCompleted method for routing, which makes the listener useless in the first place.
MaterialApp() is identical to the example provided in the docs (I am trying to navigate from the login screen, which is the initialRoute in this case, to the home screen)
the login form looks like this:
#override
Widget build(BuildContext context) {
return BlocListener<AuthenticationBloc, AuthenticationState> (
listener: (context, state) {
// first time around state is read
if (state is AuthenticationAuthenticated) {
Navigator.of(context).pushNamed(Home.routeName);
}
},
child: BlocBuilder(
bloc: _loginBloc,
builder: (BuildContext context, state) {
return FlutterLogin(
title: 'Login',
logo: const AssetImage('lib/assets/madrid.png'),
onLogin: _authUser,
onSignup: _signupUser,
onRecoverPassword: _recoverPassword,
loginProviders: <LoginProvider>[
... Providers here...
],
// if this method is omitted, I'll get a [ERROR:flutter/lib/ui/ui_dart_state.cc(209)]
onSubmitAnimationCompleted: () {
Navigator.of(context).pushNamed(Home.routeName);
},
);
},
),
);
}
I am splitting events an state between two blocs, 'AuthenticationBloc' (wraps entire app, if a token has been stored then the state will be 'AuthenticationAuthenticated') and 'LoginBloc' (used for login/logout events)
#1 when I click on the sign up button, the associated method will call _loginBloc?.add(SignUpButtonPressed(email: email, password: password))
#2 fast forward to the bloc:
LoginBloc({required this.authenticationBloc, required this.loginRepository})
: super(const SignInInitial()) {
on<SignUpButtonPressed>(_signUp);
}
...
FutureOr<void> _signUp<LoginEvent>(SignUpButtonPressed event, Emitter<LoginState> emit) async {
emit(const SignInLoading());
try {
final credentials = User(email: event.email, password: event.password);
final success = await loginRepository.signUp(credentials);
if (success) {
final token = await loginRepository.signIn(credentials);
authenticationBloc.add(LoggedIn(email: event.email, token: token));
} else {
emit(const SignInFailure(error: 'Something went wrong'));
}
} on Exception {
emit(const SignInFailure(error: 'A network Exception was thrown'));
} catch (error) {
emit(SignInFailure(error: error.toString()));
}
}
this is successful, and it triggers the authentication bloc:
AuthenticationBloc({required this.userRepository})
: super(const AuthenticationUninitialized()) {
on<LoggedIn>(_loggedIn);
}
...
FutureOr<void> _loggedIn<AuthenticationEvent>(LoggedIn event, Emitter<AuthenticationState> emit) async {
await userRepository?.persistEmailAndToken(
event.email, event.token);
await _initStartup(emit);
}
...
Future<void> _initStartup(Emitter<AuthenticationState> emit) async {
final hasToken = await userRepository?.hasToken();
if (hasToken != null && hasToken == true) {
emit(const AuthenticationAuthenticated());
return;
} else {
emit(const AuthenticationUnauthenticated());
}
}
... and at the end of this, the state is updated to AuthenticationAuthenticated, which is the expected behaviour, and the observer logs the transition as expected.
Now, this state change should trigger the navigation from within the BlocListener, but nope.
I would like to get rid of the Navigator inside the onSubmitAnimationCompleted, and rely on the state change.
I reckon this might be caused by Equatable, as my state extends that:
abstract class AuthenticationState extends Equatable {
const AuthenticationState();
#override
List<Object> get props => [];
}
class AuthenticationAuthenticated extends AuthenticationState {
const AuthenticationAuthenticated();
}
However, I've tried for hours, but I can't find anything in the docs, github, or SO that works.
So, I have not been able to get rid of the Navigator inside of onSubmitAnimationCompleted (I guess the BlocListener is disposed when the form is submitted, and before the animation is completed), but in the process I've managed to make my state management clean and robust, so I'll leave a little cheatsheet below, feel free to comment or give your opinion:
Assuming your widget's build method looks something like this:
#override
Widget build(BuildContext context) {
return BlocListener<AuthenticationBloc, AuthenticationState> (
bloc: _authenticationBloc,
listener: (context, state) {
if (state.status == AuthenticationAppState.authenticated) {
Navigator.of(context).pushNamed(Home.routeName);
}
},
child: BlocBuilder(
bloc: _loginBloc,
builder: (BuildContext context, state) {
return FlutterLogin(
...
and that your events extend Equatable
import 'package:equatable/equatable.dart';
abstract class AuthenticationEvent extends Equatable {
const AuthenticationEvent();
#override
List<Object> get props => [];
}
class LoggedIn extends AuthenticationEvent {
final String email;
final dynamic token;
const LoggedIn({ required this.email, this.token });
#override
List<Object> get props => [email, token];
}
your Bloc will look like:
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
final SecureStorage? userRepository;
AuthenticationBloc({required this.userRepository})
: super(const AuthenticationState.uninitialized()) {
on<LoggedIn>(_loggedIn);
on<LoggedOut>(_loggedOut);
on<UserDeleted>(_userDeleted);
}
...
FutureOr<void> _loggedOut<AuthenticationEvent>(LoggedOut event, Emitter<AuthenticationState> emit) async {
emit(const AuthenticationState.loggingOut());
await userRepository?.deleteToken();
// API calls here
// event has access the event's properties e.g. event.email etc
}
the state has been refactored to:
import 'package:equatable/equatable.dart';
enum AuthenticationAppState {
uninitialized,
unauthenticated,
authenticated,
loggingOut,
loading,
}
class AuthenticationState extends Equatable {
const AuthenticationState._({
required this.status,
});
const AuthenticationState.uninitialized() : this._(status: AuthenticationAppState.uninitialized);
const AuthenticationState.unauthenticated() : this._(status: AuthenticationAppState.unauthenticated);
const AuthenticationState.authenticated() : this._(status: AuthenticationAppState.authenticated);
const AuthenticationState.loggingOut() : this._(status: AuthenticationAppState.loggingOut);
const AuthenticationState.loading() : this._(status: AuthenticationAppState.loading);
final AuthenticationAppState status;
#override
List<Object> get props => [status];
}

BlocProvider inside initstate that receive value from blocbuilder

i curious how to call BlocProvider inside inistate that retrieve value from BlocBuilder.
usually inside widget i call Blocbuilder to retrieve a string and then the string value i can pass to Bloc provider so will run fetch data.
i try in inistate like below :
void initState(){
super.initState();
BlocBuilder<idoutletbloc,String>(
builder: (context,idoutlet)=>BlocProvider.of<tablelistbloc>(context).add(idoutlet);
);}
but it said "The return is void isn't Widget", as defined by anonymous closure.
how i can retrieve a value from idoutletbloc and then i can add BlocProvider.of(context).add(idoutlet) ??
i can't find it anywhere
here my bloc code
this is my bloc string value
class idoutletbloc extends Bloc<String, String>{
#override
String get initialState => '';
#override
Stream<String> mapEventToState(String idoutlet) async* {
yield idoutlet.toString();
}
}
my bloc that fetch data that need to receive a value
class viewtableactivebloc extends Bloc<String, List<ViewTableActives>>{
#override
List<ViewTableActives> get initialState => [];
#override
Stream<List<ViewTableActives>> mapEventToState(String event) async*{
List<ViewTableActives> viewtableactive =[];
try{
final response = await http.post(BaseUrl.ViewTableActive,
body: {
"ID_Outlet": event,
});
final data = jsonDecode(response.body);
if (data.length != 0) {
print("----------START Print View Table Active----------");
data.forEach((api) {
viewtableactive.add(
ViewTableActives(
api['ID_Transactions'],
api['ID_Customer'],
)
);
print("Print View Table Actibe : "
"ID_Transactions : "+api['ID_Transactions']+", "
"ID_Customer : "+api['ID_Customer']+", "
);
});
print("----------END Print View Table Active----------");
print("viewtableactivebloc : sukses");
} else {
viewtableactive =[];
print('data kosong');
}
}
catch(e){
print("Error ViewTableActive :");
print(e);
}
yield viewtableactive;
}}
You can access the current state of the bloc like:
BlocProvider.of(context).state;
or via the shorthand
context.bloc().state;

Problem using Flutter Provider when updating a value during build

I'm trying to update my uid in a provider just after checking whether a user is logged. When I do that, throws an error when building widgets even though the app does not crash. Here is the code:
class HandleAuth extends StatelessWidget {
#override
Widget build(BuildContext context) {
var user = Provider.of<FirebaseUser>(context);
if (user != null) {
print('user.uid is ${user.uid}');
final loggedUserInfo = Provider.of<LoggedUserInfo>(context, listen: false);
loggedUserInfo.updateUserInfo(user.uid);
print('first scan screen user: ${loggedUserInfo.userUid}');
}
return (user == null)
? WelcomeNewUserScreen()
: ServicesAroundMe();
}
}
And here is the provider:
class LoggedUserInfo with ChangeNotifier {
String _uid;
String get userUid {
return _uid;
}
void updateUserInfo(String updatedUid) {
_uid = updatedUid;
print('updated uid is $_uid');
notifyListeners();
}
}
It throws this error:
This ListenableProvider widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: ListenableProvider
You have to bear in mind that each time you call the method updateUserInfo, the notifyListeners() is triggered which tries to rebuild dirty widgets. Provider.of<T>(context) without the argument listen: false also does the same thing. Hence 2 rebuild trigger are called which cause the error.
When working with a provider, it is advisable to use a stream.
To make your code more scalable, create a custom class for your user and Either use ChangeNotifier or provider and streams.
For example;
class User {
final String uid;
final String displayName;
User({ #required this.uid, this.displayName });
}
class Auth {
User _firebaseUserMapper( FirebaseUser user) {
if(user == null){
return null;
}
return User(uid: user.uid, displayName: user.displayName);
}
Stream<User> get onAuthStateChange {
return Firebase.instance.onAuthStateChanged.map(_firebaseUserMapper);
}
}
In your page screen, you use like bellow
class HandleAuth extends StatelessWidget {
#override
Widget build(BuildContext context) {
final auth = Provider.of<Auth>(context, listen: false);
return StreamBuilder<User>(
stream: auth.onAuthStateChanged,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if( snapshot.connectionState == ConnectionState.active) {
User user = snapshot.data;
if (user == null ){
return WelcomeNewUserScreen();
}
return Provider<User>.value(
value: user,
child: ServicesAroundMe(),
);
}
return Scaffold(
body: Center(
child: CircularProgressIndicator();
),
);
}
);
}
The stream will forever listen for a currentUser/newUser and navigate to the appropriate page.

How to pop screen using Mobx in flutter

I have a Food object that contains properties like name, id, calories, etc. With a series of screens, the user populates the food object properties.
Once done, the user can press the submit button, that will call the addFood method in the store.
The problem is, after uploading the food to the server, i want to pop the screen or show error message in toast based on the response. I just don't know how to do this.
Following is my code (only the important bits):
FoodDetailStore.dart
class FoodDetailStore = _FoodDetailStore with _$FoodDetailStore;
abstract class _FoodDetailStore with Store {
Repository _repository;
Food _food;
#observable
String msg = '';
// ... Other Observables and actions
#action
addFood(bool toAdd) {
if (toAdd) {
_repository.addFood(food).then((docId) {
if (docId != null) {
// need to pop the screen
}
}).catchError((e) {
// show error to the user.
// I tried this, but it didn't work
msg = 'there was an error with message ${e.toString()}. please try again.';
});
}
// .. other helper methods.
}
FoodDetailScreen.dart (Ignore the bloc references, I am currently refactoring code to mobx)
class FoodDataScreen extends StatefulWidget {
final String foodId;
final Serving prevSelectedServing;
final bool fromNewRecipe;
FoodDataScreen({#required this.foodId, this.prevSelectedServing, this.fromNewRecipe});
#override
_FoodDataScreenState createState() => _FoodDataScreenState(
this.foodId,
this.prevSelectedServing,
this.fromNewRecipe,
);
}
class _FoodDataScreenState extends State<FoodDataScreen> {
final String foodId;
final Serving prevSelectedServing;
final bool fromNewRecipe;
FoodDataBloc _foodDataBloc;
_FoodDataScreenState(
this.foodId,
this.prevSelectedServing,
this.fromNewRecipe,
);
FoodDetailStore store;
#override
void initState() {
store = FoodDetailStore();
store.initReactions();
store.initializeFood(foodId);
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
// I know this is silly, but this is what i tried. Didn't worked
Observer(
builder: (_) {
_showMsg(store.msg);
}
);
}
#override
Widget build(BuildContext context) {
return Container(
// ... UI
);
}
_popScreen() {
_showMsg('Food Added');
Majesty.router.pop(context);
}
_showMsg(String msg) {
Fluttertoast.showToast(msg: msg);
}
#override
void dispose() {
store.dispose();
super.dispose();
}
}
Constructing an Observer instance inside the didChangeDependencies() is indeed "silly" as you have rightly noted already :)
Observer is a widget and widget needs to be inserted into the widgets tree in order to do something useful. In our case non-widget Mobx reactions come to the rescue.
I will show how I did it in my code for the case of showing a Snackbar upon observable change so you will get an idea how to transform your code.
First of all, import import 'package:mobx/mobx.dart';.
Then in the didChangeDependencies() create a reaction which will use some of your observables. In my case these observables are _authStore.registrationError and _authStore.loggedIn :
final List<ReactionDisposer> _disposers = [];
#override
void dispose(){
_disposers.forEach((disposer) => disposer());
super.dispose();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_authStore = Provider.of<AuthStore>(context);
_disposers.add(
autorun(
(_) {
if (_authStore.registrationError != null)
_scaffoldKey.currentState.showSnackBar(
SnackBar(
content: Text(_authStore.registrationError),
backgroundColor: Colors.redAccent,
duration: Duration(seconds: 4),
),
);
},
),
);
_disposers.add(
reaction(
(_) => _authStore.loggedIn,
(_) => Navigator.of(context).pop(),
),
);
}
I use two types of Mobx reactions here: autorun and reaction. autorun triggers the first time immediately after you crate it and then every time the observable changes its value. reaction does not trigger the first time, only when the observable change.
Also pay attention to dispose the created reactions in the dispose() method to avoid resources leak.
Here is a code of my Mobx store class with used observables to complete the picture:
import 'package:mobx/mobx.dart';
import 'dart:convert';
part "auth_store.g.dart";
class AuthStore = AuthStoreBase with _$AuthStore;
abstract class AuthStoreBase with Store{
#observable
String token;
#observable
String registrationError;
#observable
String loginError;
#action
void setToken(String newValue){
token = newValue;
}
#action
void setRegistrationError(String newValue){
registrationError = newValue;
}
#action
void setLoginError(String newValue){
loginError = newValue;
}
#action
void resetLoginError(){
loginError = null;
}
#computed
bool get loggedIn => token != null && token.length > 0;
#action
Future<void> logOut() async{
setToken(null);
}
}