MultiProvider sending NULL in child widgets but prints right value in Console - flutter

My HomePagewhere Providers are initilized:
Widget build(BuildContext context) {
return SafeArea(
child: MultiProvider(
providers: [
ChangeNotifierProvider<EmailAuth>(create: (context) => EmailAuth()),
],
child: Scaffold(
resizeToAvoidBottomInset: true,
floatingActionButton: FloatingActionButton(.....
My Authentication function that is triggered when user logs-in (Firebase)
class EmailAuth extends ChangeNotifier {
final _auth = FirebaseAuth.instance;
final dbRef = FirebaseFirestore.instance.collection("users");
String userid;
Future signIn({String email, String password}) async {
final currentUser = await _auth.signInWithEmailAndPassword(
email: email, password: password);
if (currentUser != null) {
userid = _auth.currentUser.uid;
dbRef.doc(_auth.currentUser.uid).update({
"lastLogin": DateTime.now(),
});
} else {
print("something didn't work");
}
print(userid);
notifyListeners();
return userid;
}
}
This is how my Consumer is setup in the HomePage - AppBar
title: Consumer<EmailAuth>(
builder: (context, data, child) => Text(
"${data.userid}",
style: TextStyle(color: Colors.indigoAccent),
),
),
But the output on AppBar is NULL. What am I doing wrong?!
I have been using this as reference for implementation:
https://medium.com/flutter-community/making-sense-all-of-those-flutter-providers-e842e18f45dd

Something similar was a known error in the older Provider Package. Please update to latest and check if the issue is still there. However,
This is how a MultiProvider should look like:
#override
Widget build(BuildContext context) {
return MultiProvider( // <--- MultiProvider
providers: [
ChangeNotifierProvider<MyModel>(create: (context) => MyModel()),
ChangeNotifierProvider<AnotherModel>(create: (context) => AnotherModel()),
],
And should be consumed like this
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
// We have access to the model.
myModel.doSomething();
},
);
},
)
class MyModel with ChangeNotifier { // <--- MyModel
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
notifyListeners();
}
}

Related

Widget is not rebuilding when notifyListeners is called

i am trying to create a login page so that when i am logged-in, the Navigationrail Widget lets me access all its destinations. When logged off i can only access two pages.
I am using Provider in login.dart to triger a widget rebuild in main.dart .
here is the code.
login.dart
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:httptest/depand/XmlLogin.dart';
import 'package:httptest/main.dart';
void login(String userName, String passWord) async {
Response response;
Dio dio = Dio();
dio.interceptors.add(InterceptorsWrapper(
onResponse: (response, handler) {
var token = getToken.Transcribe(response.data);
LoginProvider obj = LoginProvider();
obj.providestate(true);
print(token);
print("logged in");
handler.next(response);
},
));
try {
//Http Post method
} catch (e) {
print(e);
}
}
class LoginProvider extends ChangeNotifier {
bool loginstate = false;
void providestate(bool val) {
loginstate = val;
print("loginstate changed to $loginstate");
notifyListeners();
}
}
main.dart
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
List<Widget> pages = [Page0(), Page1(), Page2(), Placeholder()];
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: ChangeNotifierProvider<LoginProvider>(
create: (context) => LoginProvider(),
child: Builder(
builder: (context) {
return Consumer<LoginProvider>(
builder: (context, provider, child) {
return NavigationRail(
extended: constraints.maxWidth >= 600,
minExtendedWidth: 200,
destinations: [
NavigationRailDestination(),
NavigationRailDestination(),
NavigationRailDestination(),
NavigationRailDestination()
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
if (provider.loginstate) {
setState(() {
selectedIndex = value;
});
print("On");
} else {
if (value == 0 || value == 3) {
setState(() {
selectedIndex = value;
});
print("OFF");
}
}
},
);
});
},
),
)),
Expanded(
child: Scaffold(
body: IndexedStack(
index: selectedIndex,
children: pages,
),
),
),
],
),
);
});
}
}
the login goes through but i Still cant access pages 1 and 2.
it prints out 'loginstate changed to True' from login.dart.
To ensure access place a provider in a widget tree something like this:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => LoginProvider()),
...
],
child: const MyApp(),
),
);
}
Then in child widgets access it using either Consumer to listen changes or Provider.of when just calling a method.
Consumer<LoginProvider>(
builder: (context, provider, child) {
...
},
)
Provider.of<LoginProvider>(context, listen: false).login(userName, passWord);
See Simple app state management for the full example.

Navigator.of(context) is not navigating, giving null value error on context

Google signin provider class is not navigating to the next screen. Signin the user works, but the suer has to signin then leave the app and then re-open. After re-opening the app works fine with the user signed in.
Error log gives:
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Null check operator used on a null value E/flutter ( 9884): #0 StatefulElement.state (package:flutter/src/widgets/framework.dart:4789:44) E/flutter ( 9884): #1 Navigator.of (package:flutter/src/widgets/navigator.dart:2730:47) E/flutter ( 9884): #2 GoogleSignInProvider.signInwithGoogle (package:blahblah/google_signin_provider.dart:37:17) E/flutter ( 9884): <asynchronous suspension>
class GoogleSignInProvider extends ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
User user = firebaseAuth.currentUser;
final GoogleSignIn _googleSignIn = GoogleSignIn();
Future<String> signInwithGoogle(BuildContext context) async {
try {
final GoogleSignInAccount googleSignInAccount =
await _googleSignIn.signIn();
final GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
await _auth.signInWithCredential(credential);
} on FirebaseAuthException catch (e) {
print(e.message);
throw e;
}
final User currentUser = FirebaseAuth.instance.currentUser;
String uid = currentUser.uid;
if (currentUser != null)
await usersRef.doc(currentUser.uid).set({'id': uid, 'email':
currentUser.email,
'username': currentUser.displayName, 'photoUrl':
currentUser.photoURL,
'phone': '', 'dob': '', 'sex': ''});
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => TabScreen()));
notifyListeners();
return null;
}
Future logout() async {
await _googleSignIn.disconnect();
FirebaseAuth.instance.signOut();
}
}
thanks for the help!
Basically, you're facing an issue, that is so silly, you'll laugh at yourself after knowing the cause of the error (take it as sarcasm).
The main point here is, we use context to refer to our class's reference or location inside the widget tree. So, pointing the wrong context will always be error-prone. What you need to do is to point to the accurate context variable to navigate properly.
I will suggest you rename all the context variables, so you'll know which exact context you're using to navigate. What you need to do is to pass the root context variable that was provided to your state-full/less widget's build function.
See the example below:
class SignInScreen extends StatefulWidget {
#override
_SignInScreenState createState() => _SignInScreenState();
}
class _SignInScreenState extends State<SignInScreen> {
#override
Widget build(BuildContext context) {
return KeyboardDismissOnTap(
child: BlocBuilder<ThemeCubit, ThemeState>(
builder: (themeBuilderContext, state) {
final ThemeHelper theme = ThemeHelper(state.theme, false);
return Scaffold(
backgroundColor: theme.backgroundColor,
appBar: AppBar(
backgroundColor: theme.backgroundColor,
leading: IconButton(
icon: Icon(Icons.close, color: theme.accentColor),
onPressed: () => Navigator.of(context).pop(),
),
title: Text("Authentication", style: TextStyles.title(context: context, color: theme.accentColor)),
),
body: KeyboardVisibilityBuilder(
builder: (keyboardVisibilityContext, isKeyboardVisible) {
return BlocConsumer<CheckExistenceCubit, CheckExistenceState>(
listener: (checkExistenceListenerContext, state){
if(state is CheckExistenceSuccess) {
if(state.facebookSignIn || state.googleSignIn) {
final Map<String, dynamic> arguments = {
"shouldGoBack": widget.shouldGoBack,
"email": state.userId,
"first_name": state.firstName,
"last_name": state.lastName,
"profile_picture": state.profilePicture,
"facebook_sign_in": state.facebookSignIn,
"google_sign_in": state.googleSignIn,
};
Navigator.of(context).pushReplacementNamed(AppRouter.signUp, arguments: arguments);
}
}
},
builder: (CheckExistenceBuilderContext, state) {
if (state is CheckExistenceError) {
return AuthenticationForm(shouldGoBack: widget.shouldGoBack);
} else if (state is CheckExistenceNetworking) {
return AuthenticationForm(shouldGoBack: widget.shouldGoBack);
} else if (state is CheckExistenceSuccess) {
if (state.newUser) {
if (state.emailSignIn) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => VerifyOtpCubit()),
BlocProvider(create: (context) => ResendOtpCubit()),
],
child: EmailOTPVerificationWidget(email: state.userId, otpLogId: state.otpLogId, shouldGoBack: widget.shouldGoBack),
);
} else if (state.phoneSignIn) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => VerifyOtpCubit()),
BlocProvider(create: (context) => ResendOtpCubit()),
],
child: PhoneOTPVerificationWidget(phone: state.userId, otpLogId: state.otpLogId, shouldGoBack: widget.shouldGoBack),
);
} else {
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => VerifyOtpCubit()),
BlocProvider(create: (context) => ResendOtpCubit()),
],
child: EmailOTPVerificationWidget(email: state.userId, otpLogId: state.otpLogId, shouldGoBack: widget.shouldGoBack),
);
}
} else {
return AuthenticationSignInWidget(
userId: state.userId,
shouldGoBack: widget.shouldGoBack,
email: state.emailSignIn,
phone: state.phoneSignIn,
facebook: state.facebookSignIn,
google: state.googleSignIn,
firstName: state.firstName,
lastName: state.lastName,
profilePicture: state.profilePicture,
);
}
} else {
return AuthenticationForm(shouldGoBack: widget.shouldGoBack);
}
},
);
},
),
);
},
),
);
}
}
Now, look very carefully. I have named my context variables differently, except the root one.
#override
Widget build(BuildContext context) {
Then, I renamed my BlocBuilder's context as the Cubit's name.
child: BlocBuilder<ThemeCubit, ThemeState>(
builder: (themeBuilderContext, state) {
And, here also,
body: KeyboardVisibilityBuilder(
builder: (keyboardVisibilityContext, isKeyboardVisible) {
return BlocConsumer<CheckExistenceCubit, CheckExistenceState>(
listener: (checkExistenceListenerContext, state){
Look how carefully I renamed all my context variables according to their scope. People always overlook small details like this one, I was one of them until I faced the same just like you. Ask me a question, if this doesn't help you out.
Here is a reference for an in-depth explanation.
Now, what you actually need to do is to pass the root context variable to your signInwithGoogle method.
Define a navigator key that accesses from everywhere in-app and pass it to root MaterialApp navigator key property in build method
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
then :
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
//...
);
}
then you should pass navigator navigatorKey.currentContext to your Navigator.of(context) like this:
Navigator.of(navigatorKey.currentContext).pushReplacement(
MaterialPageRoute(builder: (_) => TabScreen()));
Hope this work's
Its happening due to your widget getting out of context and not in the widgetry path. Try and use Builder to see if it resolves your issue

Not getting provider data when navigating in flutter

I know flutter provider is scoped. So I declared providers (those will be needed everywhere) top of MaterialApp. In a screen am chaning a provider value and navigating to another screen. In that screen am not getting the data. Need suggestions and guide where I have done the mistake
main.dart
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<UserAuthViewModel>(
create: (context) => sl<UserAuthViewModel>()),
ChangeNotifierProvider<UserProfileViewModel>(
create: (context) => sl<UserProfileViewModel>()),
ChangeNotifierProvider<BottomNavViewModel>(
create: (context) => sl<BottomNavViewModel>()),
],
child: MaterialApp(
title: "Footsapp",
theme: ThemeData(fontFamily: 'Montserrat'),
debugShowCheckedModeBanner: false,
home: isFirstLaunch == true ? OnBoarding() : SplashView(),
routes: {
//onboarding
SplashView.SCREEN_ID: (context) => SplashView(),
//bottom nav pages
BottomNavContainer.SCREEN_ID: (context) => BottomNavContainer(),
},
),
),
);
splash screen where getting some info from api call
class _SplashViewState extends State<SplashView>
with TokenProvider, AfterLayoutMixin<SplashView> {
final userProfileViewModel = sl<UserProfileViewModel>();
final prefUtil = sl<SharedPrefUtil>();
#override
void afterFirstLayout(BuildContext context) {
Future.delayed(Duration(seconds: 1), () async {
bool isLoggedIn = prefUtil.readBool(IS_LOGGED_IN) ?? false;
bool initialProfileUpdated =
prefUtil.readBool(INITIAL_PROFILE_UPDATED) ?? false;
isLoggedIn == true
? getProfileInfo()
: await Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (_) => SocialLoginRegScreen(),
),
(route) => false);
});
}
#override
void initState() {
super.initState();
}
void getProfileInfo() async {
final userProfileResponse = await userProfileViewModel.getUserProfileData();
if (userProfileResponse.success == true) {
print('In splash: ${userProfileViewModel.userProfile.toString()}');
//from log
In splash: {firstName: Ashif, lastName: 123, birthday: 1990-02-03, email:
ashif123#gmail.com, preferredFoot: Left, preferredPosition: Midfielder,
numberOfTimesPlayedWeekly: 4}
Future.delayed(Duration(milliseconds: 500), () async {
await Navigator.pushReplacementNamed(
context,
BottomNavContainer.SCREEN_ID,
);
});
}
}
provider model class
class UserProfileViewModel extends BaseViewModel {
final _profileManageRepo = sl<ProfileManageRepo>();
Profile userProfile = Profile.initial();
Future<UserDataResponse> getUserProfileData() async {
final _userDataResponse = await _profileManageRepo.getUserProfileInfo();
if (_userDataResponse.success == true) {
userProfile = _userDataResponse.profile;
} else {
setState(ViewState.Error);
errorMessage = 'Please try again!';
}
return _userDataResponse;
}
Am trying to get the provider data (user profile) from Profile Screen. But it always get initial value.
class _UserProfileScreenState extends State<UserProfileScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Consumer<UserProfileViewModel>(
builder: (context, viewmodel, child) {
print('In profile: ${viewmodel.userProfile.toString()}');
//but here
In profile: {firstName: , lastName: , birthday: , email: , preferredFoot:
Left, preferredPosition: , numberOfTimesPlayedWeekly: 1(default value)}
return Container(
child: ProfileCardWidget(
profile: viewmodel.userProfile,
),
);
},
);
}
}

Bloc Library Null

In appbar I am trying to show profile icon after logged. When app start, appbar show profile icon, but at the same time in debug console give me an error 'A build function returned null'. When open profile page and return back, still the same error 'returned null' How to solve it?
class TestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => AuthBloc(
authService: AuthService())
..add(
AppStart(),
),
),
],
child: MaterialApp(
home: HomePage(),
),
);
}
}
homepage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return profileIcon(context);
} else if (state is UnAuthenticated) {
return logIn(context);
}
},
),
],
),
);
}
}
bloc
#override
AuthState get initialState => AuthState();
#override
Stream<AuthState> mapEventToState(AuthEvent event) async* {
if (event is AppStart) {
try {
final user = await AuthService.getCurrentUser();
yield Authenticated(user: user);
} catch (e) {
yield UnAuthenticated();
}
}
}
icon:
Widget profileIcon(context) {
return Row(
children: <Widget>[
FlatButton.icon(
icon: Icon(),
label: Text(
'Profile',
),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => UserProfile()));
},
),
],
);
}
state:
class Authenticated extends AuthState {
final FirebaseUser user;
Authenticated({this.user});
#override
List<Object> get props => [user];
}
class UnAuthenticated extends AuthState {
#override
List<Object> get props => [];
}
I'm not really sure but my guess is you have another state for your initialState which is not handled in the BlocBuilder. Even though you add AppStart event right after providing AuthBloc which will end up with either Authenticated or UnAuthenticated state, you still need to put another else for your initialState. Even if it's not the case, try to add an else statement
appBar: AppBar(
actions: <Widget>[
BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return profileIcon(context);
} else if (state is UnAuthenticated) {
return logIn(context);
} else {
return Container();
}
},
),
],
),

Unable to display string from snapshot

I would like to get data from firestore and display in the text but an eror has popup.
Thar error say type '_BroadcsastStream' is not a subtype of type 'String'.
Class i call the method
void main() => runApp(MaterialApp(
home : profUser(),
));
class profUser extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home : new profiUser(),
);
}
}
class profiUser extends StatefulWidget {
#override
_profiUserState createState() => _profiUserState();
}
class _profiUserState extends State<profiUser> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.indigo,
appBar: AppBar(
title: Text('Profile'),
actions: <Widget>[
IconButton(
icon: Icon(choices[1].icon),
onPressed: (){
logOut();
Navigator.push(context,MaterialPageRoute(builder: (context)
=> myLogin()));
},
),
],
),
body: Column(
children: <Widget>[
userDetail(),
],
),
);
}
}
Class to display text based on the data from firestore
class userDetail extends StatelessWidget{
#override
Widget build(BuildContext context){
return new StreamBuilder(
stream: UniQuery().getUserDetail(),
builder: (BuildContext context, snapshot){
if(!snapshot.hasData)
return new Text('Loading..... Please wait');
var userDocument = snapshot.data;
return new Column(
children: <Widget>[
Text('Name: ' + userDocument['name']),
Text('Age: ' + userDocument['age'],toString()),
Text('Address: ' + userDocument['address']),
Text('Result: ' + userDocument['result']),
],
);
}
);
}
}
Below the void that use in above code to get data from firestore
var userID;
Future<String> getCurrentUser() async{
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
// return uID != null ? uID : null;
final String uID = user.uid.toString();
return uID;
}
setUserDetail() async{
userID = await getCurrentUser();
}
The problem is inside getUserDetail() you are passing an async function to documents(), so that function returns a Future. Before calling documents() you should first get the string, waiting the async call to finish, and then pass the string to documents().