problem when get user data from firebase firestore depend on data from firebase auth - flutter

i have this problem, when try to get user from firebase auth using streambuilder, and then get the user data from firestore depending on the user id, always this:
userDoc.data()
return a null?
this is the code :
StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, authSnapshot) {
// If the snapshot has user data, then they're already signed in. So Navigating to the Dashboard.
if (authSnapshot.hasData && authSnapshot.data != null) {
//return const TeacherDashboard();
return StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance
.collection("users")
.doc(authSnapshot.data?.uid)
.snapshots(),
builder: (context,
AsyncSnapshot<DocumentSnapshot> userSnapshot) {
if (userSnapshot.hasData && userSnapshot.data != null) {
final userDoc = userSnapshot.data;
print(userDoc!.get('isTeacher'));
final user = (userDoc != null
? userDoc.data()
: {"isTeacher": 0}) as Map<String, dynamic>;
if (user['isTeacher'] == 1) {
return const TeacherDashboard();
} else {
return const StudentsScreen();
}
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
});

I assume You want to know the user is a teacher or a student. if teacher, go to teacher page, if student go to student page. and you are using a value to detect the user is a teacher or student. the value is 1.
so, if user value is == 1 go to teacher page. or go to student page.
if you want this function only you do not need to create a streambuilder here. you just need to get the user value. That you can achieve like this:
// Here I created one HomePage to decide which Screen to visit.
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int? _value;
#override
void initState() {
super.initState();
getUserValue();
}
void getUserValue() async {
DocumentSnapshot snap = await FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.get();
setState(() {
_value = (snap.data() as Map<String, dynamic>)['isTeacher'];
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _value == null
? const Center(
child: CircularProgressIndicator(),
)
: (_value == 1)
? const TeacherDashboard()
: const StudentsScreen(),
);
}
}
sidenote: I think you getting the error because You using Stateless widget. It's very important to use a Stateful widget and initially keep the value null. and if value is null show something like CircularProgressIndicator(). once value is available go to different Screen. in Stateless widget once the widget is built already it will get the value but will not rebuilt anything. so null value will decide your widget what gives you the error. and You must setState() Once you get the value.
Hope this will solve your problem.

Related

How To get The Total of values in Flutter firestore? elevated button text

so below iam able to get the total sum of my prices from firestore but i cant seem to be able to call it to text in elevated button here is my code the total sum comes to me correct as i said but the thing is calling the final value to my button any help will be appreciated
class cartpage extends StatefulWidget {
const cartpage({Key? key}) : super(key: key);
#override
State<cartpage> createState() => _cartpageState();
}
class _cartpageState extends State<cartpage> {
AuthService get _auth => AuthService();
final Stream<QuerySnapshot> Cart = FirebaseFirestore.instance
.collection('Cart')
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection("UserCart")
.doc('test')
.collection('final')
.snapshots();
var total = FirebaseFirestore.instance
.collection('Cart')
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection("UserCart")
.doc('test')
.collection('final')
.get()
.then((querySnapshot) {
num sum = 0.0;
querySnapshot.docs.forEach((element) {
num value = element.data()["Price"];
sum = sum + value;
});
return sum;
});
#override
Widget build(BuildContext context) {
return // i removed some of the code from here //
ElevatedButton(
onPressed: null,
child: Text('$sum'),
)
],
)
],
);
}
}
Update this is my current code i get the sum and it shows in the button but as i mentioned when on this cart page and want to remove something from cart the changes doesnt apply.
FutureBuilder(
future: FirebaseFirestore.instance
.collection('Cart')
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection("UserCart")
.doc('test')
.collection('final')
.get(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> QuerySnapshot) {
if (QuerySnapshot.hasError) {
return Text("Something went wrong");
}
if (QuerySnapshot.connectionState == ConnectionState.done) {
QuerySnapshot.data!.docs.forEach((doc) {
sumtotal = sumtotal +
doc["Price"]; // make sure you create the variable sumTotal somewhere
});
return ElevatedButton(
onPressed: null,
child: Text('Submit total price RM ${sumtotal}'));
}
return Text("loading");
},
Your Firebase request returns you some data correctly, but you should remember that it takes some time. In your example you are trying to use sum variable, but this variable does not receive the data form Firebase. You should first display some Loading (e.q.: CircularProgressIndicator widget) and call a Firebase request. When you receive the response, then you can change the state and pass sum to your widget.
So create asynchronous method and move your Firebase request call there with await keyword.
PS. So you use some state management? e.g: BLoC?

Null check operator used on a null value problem, I am very confused

I am new in flutter app.
I have made a subcollection products in users collections. It will show to all when a user will log in to their account. When the user clicks on the My Products button it will only show those products which are created by the login user. I user stream builder and use this FirebaseFirestore.instance
.collection('users')
.doc(LoginUser!.uid)
.collection('products')
.snapshots() , to get the data.
But when I click on the button it throws an exception. Which provide on the screen shots.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class UserProductList extends StatefulWidget {
UserProductList({Key? key}) : super(key: key);
#override
_UserProductListState createState() => _UserProductListState();
}
class _UserProductListState extends State<UserProductList> {
User? LoginUser;
#override
void initState() {
super.initState();
getCurrentUser();
}
void getCurrentUser() async{
var LoginUser=await FirebaseAuth.instance.currentUser;
print(LoginUser!.email);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('users')
.doc(LoginUser!.uid)
.collection('products')
.snapshots() ,
builder:(BuildContext, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot){
return ListView(
children: snapshot.data!.docs.map((document){
return ElevatedButton(onPressed: getCurrentUser, child: Text('data'));
}).toList(),
);
},
),
);
}
}
FIrst of all, FirebaseAuth.instance.currentUser is not a Future it doesn't need to be awaited. You can use it straight away in your StreamBuilder
.doc(FirebaseAuth.instance.currentUser?.uid ?? '')
My mistake was by making the currentUser future by using async and await. that's why steamBulder did not get the user id to fetch the data and throwing error for null user.
void getCurrentUser() async{
var LoginUser=await FirebaseAuth.instance.currentUser;
print(LoginUser!.email);
}```
So, I just remove this portion code and instead of that I just use this **var LoginUser = FirebaseAuth.instance.currentUser;** to get my **login user Uid** and it's working perfectly

How to Reload Flutter StatefulWidget with AutomaticKeepAliveClientMixin?

How to Reload Flutter StatefulWidget with AutomaticKeepAliveClientMixin?
The below code is Not reloading the Usermovies list StreamBuilder on user logout through firebase, instead showing old user movies data only.
This HomeScreen is called in Bottom Navigation Bar with PageView. The other Page is AccountScreen with Login and Logout buttons.
My question is how to reload the UserMovies on user logout through firebase. How to reload the HomeScreen on logout from AccountScreen such that the User Movies Stream is refreshed to null.
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
// need to call super method for AutomaticKeepAliveClientMixin
super.build(context);
print('Rebuild in Home Screen.....');
return StreamBuilder<app.User>(
stream: Provider.of<AuthProvider>(context, listen: true).user,
builder: (context, snapshot) {
if (snapshot.data != null) {
isUserLoggedIn = true;
rebuild = false;
} else if (snapshot.data == null && isUserLoggedIn) {
isUserLoggedIn = false;
rebuild = true;
} else {
isUserLoggedIn = false;
rebuild = false;
}
if (rebuild) {
// Not reloading the Usermovies on user logout, instead showing old user movies data only in the below stream builder
Future.delayed(Duration.zero, () => setState(() {}));
}
return StreamBuilder<List<UserMovies>>(
stream: Provider.of<UserDetailsProvider>(context,
listen: false)
.getUserFavouriteMovies(),
builder: (context, snapshot) {
snapshot.data != null && snapshot.data.length > 0
? print('data there: ')
: print('data zero');
snapshot.data != null && snapshot.data.length > 0
? Scaffold.of(context).showCurrentSnackBar() // to show last favourite movie
: Scaffold.of(context).hideCurrentSnackBar();
return SizedBox(height: 2.0);
},
},
),
}
}
return a check on whether the user exists or not
#override
bool get wantKeepAlive => isUserLoggedIn;;
in the same class listen for your user stream, and keep track of whether the user present or not and set isUserLoggedIn based on that, now state will be maintained if the user exists otherwise not.
initState(){
Provider.of<AuthProvider>(context, listen: true).user.listen((user){
isUserLoggedIn = user!=null;
});
}
here wantKeepAlive is a framework getter method, which is used by flutter framework (the mixin) to decide whether the state must be maintained or not, you can return a boolean which can be dynamic depending on your needs.

Error: Could not find the correct Provider<WgService> above this landing widget

class Landing extends StatelessWidget {
const Landing({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
AuthService auth = Provider.of<AuthService>(context);
return StreamBuilder<FirebaseUser>(
stream: auth.onAuthStateChanged,
builder: (contexta, snapshot) {
FirebaseUser user = snapshot.data;
WgService wg = Provider.of<WgService>(context);
if (user == null)
return LoginView();
else
return StreamBuilder<WGDocument>(
stream: wg.streamWG('demowg'),
builder: (contextWG, snapshotWG) {
WGDocument currentWG = snapshotWG.data;
if (currentWG != null)
return SignedInView();
else
return JoinWGScreen();
});
});
}
}
I readed multiple issues with the same error but cant get it fixed by myself. I tried every other context and I do not understand why the error occurs. No IDE errors given.
You need to put a Provider widget on top of your widget. Then you build your widgets as an ancestor of that provider widget. Any descendant can reach the data class of that provider.
Provider<AuthService>(
create: (_) => AuthService(),
child: /* Any widgets below can reach AuthService */
)

Check constraint and return the body accordingly

I want to show onboarding screen only for the first time user opens the application, so at the final page of Onboarding screen I put OnBoardingStatus value to be "Done" and move to the main screen. But when user opens the application for the next time this code flash the Onboarding screen for few milliseconds and then opens the mainScreen.
Here is my code
class App2 extends StatefulWidget {
App2({Key key}) : super(key: key);
#override
_App2State createState() => _App2State();
}
class _App2State extends State<App2> {
String onBoardingStatus;
#override
void initState() {
// TODO: implement initState
getOnBoardingStatus();
super.initState();
}
Future<void> getOnBoardingStatus() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
var onboardingstatus = prefs.getString('OnBoardingStatus');
setState(() {
onBoardingStatus = onboardingstatus;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: onBoardingStatus != null
? MainScreen()
: OnboardingScreen());
}
}
Currently you have no way to know if onBoardingStatus is null because the SharedPreferences instance hasn't been retrieved yet, or because the OnBoardingStatus really is empty. You can work around this with a FutureBuilder:
class App2 extends StatelessWidget {
App2({Key key}) : super(key: key);
Future<String> getOnBoardingStatus() async =>
(await SharedPreferences.getInstance()).getString('OnBoardingStatus');
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getOnBoardingStatus(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
//TODO: Return a widget that indicates loading
}
return Scaffold(
body: snapshot.data != null
? MainScreen()
: OnboardingScreen());
},
);
}
}
However I don't think it's the best solution. For starters, App2 should get the status from an outer source - this way if you ever decide to change your storage solution you wouldn't need to touch App2.