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

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

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?

Flutterfire GetX AuthFlow

I am using flutter, firebase auth, and getx to build an app. After checking if the user exists, the page should redirect normally. But it is not. What am I doing wrong? I have been stuck on this for some time now but I cannot figure it out. It is stuck on the loading screen especially when the user is null. If I create a user in firebase, it redirects normally.
class AuthController extends GetxController {
static AuthController authInstance = Get.find();
final phone = ''.obs;
final phoneOtp = ''.obs;
final verificationID = ''.obs;
final FirebaseAuth auth = FirebaseAuth.instance;
late Rx<User?> firebaseUser;
#override
void onReady() {
super.onReady();
firebaseUser = Rx<User?>(auth.currentUser);
firebaseUser.bindStream(auth.userChanges());
ever(firebaseUser, _setInitialScreen);
}
_setInitialScreen(User? user) {
if (user == null) {
Get.offAllNamed("/onboarding");
} else {
Get.offAllNamed("/landing");
}
}
}
Firebase has a function to check the user's status, use it instead it's easier and more efficient. Use a streambuilder
StreamBuilder(
stream: AuthServices().onChangedUser,
builder: (context, snapshot) {
return snapshot.data == null ? Get.offAllNamed("/onboarding"); : Get.offAllNamed("/landing");;
},
),

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

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.

Nested Future in Flutter

I'm new to Flutter, (comming from web and especially JS/VueJS)
I'm have a db in firebase that has a collection called edito and inside, i have different artist with a specific Id to call Deezer Api with it.
So what i want to do is first called my db and get the Id for each of artist and then put this id in a function as parameter to complete the url.
I did 2 Future function, one to call the db and one to call the api.
But i don't understand how to use one with the others in the build to get a listview with the information of the api of deezer for each data.
i'm getting the list but it's stuck in and endless loop.
All of my app will be on this nested function, is it possible to do this and call it in any widget that i want ?
here is my code, thanks
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class GetAlbum extends StatefulWidget {
#override
_GetAlbumState createState() => _GetAlbumState();
}
class _GetAlbumState extends State<GetAlbum> {
Map mapResponse;
Future<QuerySnapshot> getDocument() async{
return FirebaseFirestore.instance.collection("edito").get();
}
Future<dynamic> fetchData(id) async{
http.Response response;
response = await http.get('https://api.deezer.com/album/' + id);
if(response.statusCode == 200){
setState(() {
mapResponse = json.decode(response.body);
});
}
}
Future<dynamic> getDocut;
Future<dynamic> getArtist;
#override
void initState() {
getDocut = getDocument();
getArtist = fetchData(null);
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot>(
future : getDocut,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if(!snapshot.hasData) {
return CircularProgressIndicator();
}else{
return new ListView(
children: snapshot.data.docs.map<Widget>((document){
print(document.data().length);
return FutureBuilder(
future: fetchData(document.data()['idDeezer'].toString()),
builder: (context, snapshot){
return Container(
child: mapResponse==null?Container(): Text(mapResponse['title'].toString(), style: TextStyle(fontSize: 30),),
);
}
);
}).toList(),
);
}
},
);
}
}
Here's a simplified example of making two linked Future calls where the 2nd depends on data from the first, and using the results in a FutureBuilder:
import 'package:flutter/material.dart';
class FutureBuilder2StatefulPage extends StatefulWidget {
#override
_FutureBuilder2StatefulPageState createState() => _FutureBuilder2StatefulPageState();
}
class _FutureBuilder2StatefulPageState extends State<FutureBuilder2StatefulPage> {
Future<String> _slowData;
#override
void initState() {
super.initState();
_slowData = getAllSlowData(); // combined async calls into one future
}
// linked async calls
Future<String> getAllSlowData() async {
int id = await loadId(); // make 1st async call for id
return loadMoreData(id: id); // use id in 2nd async call
}
Future<int> loadId() async {
int _id = await Future.delayed(Duration(seconds: 2), () => 42);
print('loadId() completed with: $_id'); // debugging
return _id;
}
Future<String> loadMoreData({int id}) async {
return await Future.delayed(Duration(seconds: 2), () => 'Retrieved data for id:$id');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FutureBldr Stateful'),
),
body: FutureBuilder<String>(
future: _slowData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(child: Text(snapshot.data));
}
return Center(child: Text('Loading...'));
},
),
);
}
}
This avoids having to nest the FutureBuilder which may be error prone.
And calling future methods directly from a FutureBuilder is not recommended since the call could be made many times if its containing widget is rebuilt (which can happen a lot).
I tried to add firebase in the first one but i get null for the id in the get AllSlowDAta but i got it right with the Future.delayed.
// linked async calls
Future<String> getAllSlowData() async {
String id = await loadId(); // make 1st async call for id
return loadMoreData(id: id); // use id in 2nd async call
}
Future<dynamic> loadId() async {
//return await Future.delayed(Duration(seconds: 2), () => '302127');
await FirebaseFirestore.instance.collection("edito")
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
return doc.data()["idDeezer"];
});
});
}
Future<dynamic> loadMoreData({String id}) async {
http.Response response;
response = await http.get('https://api.deezer.com/album/' + id);
if(response.statusCode == 200){
setState(() {
return json.decode(response.body);
});
}
}

Flutter - How to pass user data to all views

I'm new to the flutter world and mobile app development and struggling with how I should pass user data throughout my app.
I've tried several things, but none seem great and I'm sure there are best practice patterns I should be following.
Because it makes examples easier, I'm using firebase for authentication.
I currently have a separate route for logging in. Once I'm logged in I want the User model in most views for checking permissions on what to show, displaying user info in the drawer, etc...
Firebase has an await firebaseAuth.currentUser(); Is it best practice to call this everywhere you might need the user? and if so, where is the best spot to place this call?
The flutter codelab shows a great example of authenticating users before allowing writes. However, if the page needs to check auth to determine what to build, the async call can't go in the build method.
initState
One method I've tried is to override initState and kick off the call to get the user. When the future completes I call setState and update the user.
FirebaseUser user;
#override
void initState() {
super.initState();
_getUserDetail();
}
Future<Null> _getUserDetail() async {
User currentUser = await firebaseAuth.currentUser();
setState(() => user = currentUser);
}
This works decent but seems like a lot of ceremony for each widget that needs it. There is also a flash when the screen loads without the user and then gets updated with the user upon the future's completion.
Pass the user through the constructor
This works too but is a lot of boilerplate to pass the user through all routes, views, and states that might need to access them. Also, we can't just do popAndPushNamed when transitioning routes because we can't pass a variable to it. We have to change routes similar to this:
Navigator.push(context, new MaterialPageRoute(
builder: (BuildContext context) => new MyPage(user),
));
Inherited Widgets
https://medium.com/#mehmetf_71205/inheriting-widgets-b7ac56dbbeb1
This article showed a nice pattern for using InheritedWidget. When I place the inherited widget at the MaterialApp level, the children aren't updating when the auth state changed (I'm sure I'm doing it wrong)
FirebaseUser user;
Future<Null> didChangeDependency() async {
super.didChangeDependencies();
User currentUser = await firebaseAuth.currentUser();
setState(() => user = currentUser);
}
#override
Widget build(BuildContext context) {
return new UserContext(
user,
child: new MaterialApp(
title: 'TC Stream',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new LoginView(title: 'TC Stream Login', analytics: analytics),
routes: routes,
),
);
}
FutureBuilder
FutureBuilder also seems like a decent option but seems to be a lot of work for each route. In the partial example below, _authenticateUser() is getting the user and setting state upon completion.
#override
Widget build(BuildContext context) {
return new FutureBuilder<FirebaseUser>(
future: _authenticateUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildProgressIndicator();
}
if (snapshot.connectionState == ConnectionState.done) {
return _buildPage();
}
},
);
}
I'd appreciate any advice on best practice patterns or links to resources to use for examples.
I'd recommend investigating inherited widgets further; the code below shows how to use them with asynchronously updating data:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(new MaterialApp(
title: 'Inherited Widgets Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('Inherited Widget Example'),
),
body: new NamePage())));
}
// Inherited widget for managing a name
class NameInheritedWidget extends InheritedWidget {
const NameInheritedWidget({
Key key,
this.name,
Widget child}) : super(key: key, child: child);
final String name;
#override
bool updateShouldNotify(NameInheritedWidget old) {
print('In updateShouldNotify');
return name != old.name;
}
static NameInheritedWidget of(BuildContext context) {
// You could also just directly return the name here
// as there's only one field
return context.inheritFromWidgetOfExactType(NameInheritedWidget);
}
}
// Stateful widget for managing name data
class NamePage extends StatefulWidget {
#override
_NamePageState createState() => new _NamePageState();
}
// State for managing fetching name data over HTTP
class _NamePageState extends State<NamePage> {
String name = 'Placeholder';
// Fetch a name asynchonously over HTTP
_get() async {
var res = await http.get('https://jsonplaceholder.typicode.com/users');
var name = json.decode(res.body)[0]['name'];
setState(() => this.name = name);
}
#override
void initState() {
super.initState();
_get();
}
#override
Widget build(BuildContext context) {
return new NameInheritedWidget(
name: name,
child: const IntermediateWidget()
);
}
}
// Intermediate widget to show how inherited widgets
// can propagate changes down the widget tree
class IntermediateWidget extends StatelessWidget {
// Using a const constructor makes the widget cacheable
const IntermediateWidget();
#override
Widget build(BuildContext context) {
return new Center(
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: const NameWidget()));
}
}
class NameWidget extends StatelessWidget {
const NameWidget();
#override
Widget build(BuildContext context) {
final inheritedWidget = NameInheritedWidget.of(context);
return new Text(
inheritedWidget.name,
style: Theme.of(context).textTheme.display1,
);
}
}
I prefer to use Services with Locator, using Flutter get_it.
Create a UserService with a cached data if you like:
class UserService {
final Firestore _db = Firestore.instance;
final String _collectionName = 'users';
CollectionReference _ref;
User _cachedUser; //<----- Cached Here
UserService() {
this._ref = _db.collection(_collectionName);
}
User getCachedUser() {
return _cachedUser;
}
Future<User> getUser(String id) async {
DocumentSnapshot doc = await _ref.document(id).get();
if (!doc.exists) {
log("UserService.getUser(): Empty companyID ($id)");
return null;
}
_cachedUser = User.fromDocument(doc.data, doc.documentID);
return _cachedUser;
}
}
Then create create a Locator
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton(() => new UserService());
}
And instantiate in main()
void main() {
setupLocator();
new Routes();
}
That's it! You can call your Service + cachedData everywhere using:
.....
UserService _userService = locator<UserService>();
#override
void initState() {
super.initState();
_user = _userService.getCachedUser();
}
I crashed into another problem because of this problem you can check it out here
So the solution I came up with is a bit untidy,I created a separate Instance dart page and imported it to every page.
GoogleSignInAccount Guser = googleSignIn.currentUser;
FirebaseUser Fuser;
I stored the user there on login and checked on every StateWidget if it was null
Future<Null> _ensureLoggedIn() async {
if (Guser == null) Guser = await googleSignIn.signInSilently();
if (Fuser == null) {
await googleSignIn.signIn();
analytics.logLogin();
}
if (await auth.currentUser() == null) {
GoogleSignInAuthentication credentials =
await googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
}
This is my old code I did cleaned it up on my current app but I don't have that code now in handy. Just check out for null user and log it in again
I did it for most of the Firebase instances too because I have more than 3 pages on my app and Inherited Widgets was just too much work
You can use the GetX package to check whether or not the user is logged in, get user data and have it accessible throughout your app
For my lazy mathod,
i just create new file like userdata.dart and then put any variable on it for example like dynamic Profile = null
inside userdata.dart
//only put this or anything u want.
dynamic Profile = null;
at startingpage.dart
//import that file
import '../userdata.dart';
class startingpage extends ...{
...
//set data to store..
Profile = 'user profile';
...
}
to use the data just declare and use in
anotherpage.dart
//import that file
import '../userdata.dart';
class anotherpage extends...{
...
}
class .. State ...{
...
//set the data to variable
dynamic userdata = Profile;
print('this is my lazy pass data' + userdata.toString());
...
}