Firebase doesn't work cause of null-safety (DART/FLUTTER) - firebase

I'm using/learning Firebase for my database works. My snapshot's coming like _jsonQuerySnapshot or _jsonDocumentSnapshot. But it had to be QuerySnapshot or DocumentSnapshot. Because of this I have to encode and decode my snapshot for use my datas.
If I'm not using encode decode json I'm getting null or object errors all the time.
Here is my class extends from state
class _MyHomePageState extends State<MyHomePage> {
final _firestore = FirebaseFirestore.instance;
#override
Widget build(BuildContext context) {
CollectionReference moviesRef=_firestore.collection('movies');
DocumentReference babaRef = _firestore.collection('movies').doc('Baba');
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(
title: Text('FireStore Crud'),
),
body: Center(
child: Container(
child: Column(
children: [
StreamBuilder<QuerySnapshot>(
stream: moviesRef.snapshots(),
builder: (BuildContext context,AsyncSnapshot asyncSnapshot){
List<DocumentSnapshot>listOfDocumentSnapshot=asyncSnapshot.data.docs;
return Flexible(
child: ListView.builder(
itemCount: listOfDocumentSnapshot.length,
itemBuilder: (context,index){
Text('${listOfDocumentSnapshot[index].data()['name']}' ,style: TextStyle(fontSize: 24),);
},
),
);
},
),
],
),
),
),
);
}
}
and this is my error .

First of all, check your data is null or not and then use [] on it. Probably, listOfDocumentSnapshot[index].data() is null. If it is null, render another UI such as loading screen. Namely, your loading screen must be showed until reach the data.
for example:
builder: (BuildContext context,AsyncSnapshot asyncSnapshot){
List<DocumentSnapshot>? listOfDocumentSnapshot = asyncSnapshot.data.docs;
if(!listOfDocumentSnapshot.hasData || listOfDocumentSnapshot == null){
return LoadingScreen(); //etc.
}
return Flexible(
child: ListView.builder(
itemCount: listOfDocumentSnapshot.length,
itemBuilder: (context,index){
Text('${listOfDocumentSnapshot[index].data()['name']}' ,style: TextStyle(fontSize: 24),);
},
),
);
},
Futures (asynchronous programmes) need some time to get data and you have to make your UI wait until you get your data. e.g. database connections, read/write somethings to/from somewhere etc.
For more detail you can read this article.

Related

How do I fetch sub collection from firestore using flutter?

I wanna fetch the data inside the Guests like guestName. The code below I used it to fetch collection and it worked, but I have no idea how to fetch subcollection. I been trying but it's not working.
body: CustomScrollView(
slivers: [
SliverPersistentHeader(pinned: true, delegate: SearchBoxDelegate()),
StreamBuilder<QuerySnapshot>(
//I used this to fetch collection but it doesn't work with subcollection
stream: Firestore.instance
.collection("Guests")
.orderBy("Date", descending: true).snapshots(),
builder: (context, dataSnapshot) {
return !dataSnapshot.hasData
? SliverToBoxAdapter(
child: Center(
child: circularProgress(),
),
)
: SliverStaggeredGrid.countBuilder(
crossAxisCount: 1,
staggeredTileBuilder: (c) => StaggeredTile.fit(1),
itemBuilder: (context, index) {
ItemModel model = ItemModel.fromJson(
dataSnapshot.data.documents[index].data);
return sourceInfo(model, context);
},
itemCount: dataSnapshot.data.documents.length,
);
},
),
],
),
Container(
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: Text(
// this guestName i wanna fetch
model.guestName,
style: TextStyle(
color: Colors.black, fontSize: 14.0),
),
),
],
),
),
To get a CollectionReference for a subcollection, you first need to have a DocumentReference object to its parent document, and then you can call collection() on that.
In your builder you have dataSnapshot.data.documents[index], which gives you a DocumentSnapshot object, from which you can get a DocumentReference by calling reference on it.
So combined that'd be something like this in your builder:
dataSnapshot.data.documents[index].reference.collection("Guests")
I recommend always keeping the reference documentation handy that I linked above, as it's the easiest way to find this type of API path.

Flutter, Dart, Firestore: How can I send user data retrieved from firestore to a different screen?

I have a screen where a list of users are shown using stream builder to retrieve user data from firestore.
StreamBuilder(
stream: Collection
.where('WorkType', isEqualTo: widget.worktype)
.snapshots(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Container(
height: 600,
child: ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 100,
child: GestureDetector(
onTap: () {
//Navigate to Screen two
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
snapshot.data.docs[index].data()['Name'],
style: kRobotoSlab.copyWith(
fontSize: 20)),
Text(
snapshot.data.docs[index]
.data()['Address'],
style: kRobotoSlab.copyWith(
fontSize: 15),
),
Text(
snapshot.data.docs[index]
.data()['Phone Number'],
style: kRobotoSlab.copyWith(
fontSize: 15),
),
],
),
),
),
),
),
);
}),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return CircularProgressIndicator();
}
},
),
I want that once a user clicks on the card it will navigate to screen 2 which will show the user profile with the data retrieved from firestore
e.g. Card 1 : Name => Samia Address=> USA Number=> 4659848668 user will press on it and it will navigate to screen 2 with the information of Samia's.
How can I achieve it?
Let's say you call the new screen DetailScreen, you should have it take a Map of preferably an object of the items you want to display in it for example:
in the DetailScreen definition you can have:
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Item.
final User user;
// In the constructor, require a user.
DetailScreen({Key? key, required this.user}) : super(key: key);
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(user.name),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(user.address),
),
);
}
}
Then in your GestureDetector onTap method you can then do something like:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(user: users[index]),
),
);
Since you are not using a model like my example above, you can make the second screen accept a Map field, then you pass in snapshot.data.docs[index].data() as the value when you are navigating to it.
So this will now read:
class DetailScreen extends StatelessWidget {
// Declare a field that holds the Item.
final Map user;
// In the constructor, require a user map.
DetailScreen({Key? key, required this.user}) : super(key: key);
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(user['Name']),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(user['Address']),
),
);
}
}
And while navigating you just do
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(user: snapshot.data.docs[index].data()),
),
);

Flutter/Firestore: Displaying gridtiles in categories

We are creating a recipe app for a school project.
We are using Dart/Flutter for the language and we have recipes stored in a Firestore DB collection called 'recipes' which have sub-collections of ingredients, comments and method. Inside the ingredients collection, there is a field called 'proteins' that contains an array of proteins (beef, pork, poultry, etc)
I have managed to make one big grid view which displays thumbnails of all the recipes currently stored in the DB, but we want to set them in categories by their proteins. I managed to make the individual lists for the categories but they each contain all of the recipes. I don't know now which direction to go to somehow search through the DB and then display them on the page.
This is the code for the current list that is being created.
I thought about somehow creating a search function that would create an array of document ID's which would be then used in the compiling of the lists, but not sure where to start
I'm just trying to get some nudge in the direction of how it would be done and not the code. The process of it if you will.
Thanks in advance
child: Container(
alignment: Alignment.bottomRight,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height - 100.0,
child: ListView.builder(
itemCount: 4,
itemBuilder: (context, index) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'${categories[index]}',//displays the protein name (beef
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 25.0),
),
),
),
Container(
height: 180.0,
child: StreamBuilder(
stream: firestoreDb,
builder: (
context,
snapshot,
) {
if (!snapshot.hasData)
return CircularProgressIndicator();
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.docs.length,
itemBuilder: (context, int index) {
return GestureDetector(
child: Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10)),
child: TestGridTile(
snapshot: snapshot.data,
index: index,
),
),
);
});
}),
),
],
),
),
)),
EDIT TestGridTile code as requested
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:youth_food_movement/recipe/ui/ingredients_page.dart';
//card that displays the recipe information
class TestGridTile extends StatelessWidget {
//snapshot of the database
final QuerySnapshot snapshot;
final int index;
const TestGridTile({Key key, this.snapshot, this.index}) : super(key: key);
static String idNumber;
#override
Widget build(BuildContext context) {
//snaphot of the docs
// ignore: unused_local_variable
var snapshotData = snapshot.docs[index];
var docID = snapshot.docs[index].id;
String recipeID = docID.toString();
return Container(
width: 150,//MediaQuery.of(context).size.width,
height: 150,//MediaQuery.of(context).size.height * 0.25,
//get the image URL
child: FutureBuilder(
future: _getImageURL(docID),
builder: (context, snapshot) {
if (snapshot.hasData) {
//return the image and make it cover the container
return GestureDetector(
child: Image.network(
snapshot.data,
fit: BoxFit.fill,
),
onTap: () {
idNumber = recipeID;
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
IngredientsPage(recipeID)));
},
);
} else {
return Container(
child: Center(
child: CircularProgressIndicator(),
));
}
}),
);
}
//method to get the image URL
Future _getImageURL(var docID) async {
//declare and instantiate the firebase storage bucket
final FirebaseStorage storage = FirebaseStorage.instanceFor(
bucket: 'gs://youth-food-movement.appspot.com');
//ref string will change so the parameter will be the jpg ID (maybe)
String downloadURL =
await storage.ref('recipe_images/$docID').getDownloadURL();
return downloadURL;
}
}```
I have attached an image of how it looks currently[![current image][1]][1]
[1]: https://i.stack.imgur.com/HiaYi.jpg
Because the ingredience is a collection we don't get it with the with the initial data. You have two options:
As it is now load all recipes and for each of them the ingrediences and for each category filter out the recipes that don't fit in it and show only those that do fit in. If you would share more code I could help you with it. What is behind TestGridTile.
Load for each category only the recipes that fin it by using a query with where clause and the arrayContaines. But for that the array proteins can't be nested in the ingredience subcollection of the collection you are filtering.
Both of the would be much easier if you would move the proteines array to the doc of the recipes collection. You could do that with the client side code or even with Firebase Cloud Functions.

How to get data from two collections in firebase using Flutter

This is my problem:
I have a ListPost StatefulWidget where I want to display a list of widgets that contains the user's account image, the user's name, and the user's posts images(similar to Facebook feeds), however, I have gotten to the point that I need to get that data from two different collections in Firebase (see my firebase collections image below).
The good thing is that I have been able to get that data only from one collection(userFeed) and display that data in my ListPost file in different widgets, however, I do not know how to get data from another collection in Firebase using the same streamBuilder and display all that data I want to display in other widgets in my ListPost screen.
So, my specific question is:
How can I make my ListPost screen to populate data from 2 different collections in Firebase using a stream builder or another type of implementation?
This is the firebase image
This is the complete code for the ListPost screen
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'models/post_model.dart';
final _stream = Firestore.instance.collection('userFeed').snapshots();
class ListPosts extends StatefulWidget {
#override
_ListPostsState createState() => _ListPostsState();
}
class _ListPostsState extends State<ListPosts> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
//this is the Streambuilder to get the data however it only lets me to get one collection
child: StreamBuilder(
stream: _stream,
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int data) {
//here I get the data from the userFeed colecction
Post post = Post.fromDoc(snapshot.data.documents[data]);
return Column(
children: <Widget>[
GestureDetector(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 10.0,
),
child: Row(
children: <Widget>[
CircleAvatar(
radius: 25.0,
backgroundColor: Colors.grey,
backgroundImage: post.imageUrl.isEmpty
? AssetImage(
'assets/images/user_placeholder.jpg')
: CachedNetworkImageProvider(post.imageUrl),
),
SizedBox(width: 8.0),
Text(
post.caption,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
GestureDetector(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
image:
CachedNetworkImageProvider(post.imageUrl),
fit: BoxFit.cover,
),
),
),
],
),
),
],
);
},
);
},
),
),
);
}
}
UPDATE 05-22-2020 HOW I FIXED THE ISSUE
Credits to the user griffins, he helped me to fix this issue.
This is what I do:
I nested my StreamBuilder so I can use 2 streams at the same time
return StreamBuilder(
stream: _stream,
builder: (context, snapshot1) {
return StreamBuilder(
stream: _stream2,
builder: (context, snapshot2) {
if (!snapshot2.hasData) return const Text('Loading...');
if (!snapshot1.hasData) return const Text('Loading...');
return ListView.builder(
itemExtent: 550.0,
itemCount: snapshot2.data.documents.length,
itemBuilder: (BuildContext context, int data) {
User user = User.fromDoc(snapshot2.data.documents[data]);
Post post = Post.fromDoc(snapshot1.data.documents[data]);
return buildbody(user, post, context);
},
);
},
);
},
);
You can can make you body take a widget ListView and for the Listview children have both your lists.
example
body: ListView(
children: <Widget>[
---list1----
--list2-----
]);
or you can use a custom scroll view
return new Scaffold(
appBar: new AppBar(
title: new Text("Project Details"),
backgroundColor: Colors.blue[800]),
body:
new CustomScrollView(
slivers: <Widget>[
new SliverPadding(padding: const EdgeInsets.only(left: 10.0,right: 10.0,
top: 10.0,bottom: 0.0),
sliver: new SliverList(delegate:
new SliverChildListDelegate(getTopWidgets())),
),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getSfListTiles()
))),
new SliverPadding(padding: const EdgeInsets.all(10.0),
sliver: new SliverList(delegate: new SliverChildListDelegate(
getWorkStatementTiles()
))),
]
)
);
update
from #RĂ©mi Rousselet answer You can nest StreamBuilder
StreamBuilder(
stream: stream1,
builder: (context, snapshot1) {
return StreamBuilder(
stream: stream2,
builder: (context, snapshot2) {
// do some stuff with both streams here
},
);
},
)

Gridview.builder with Firebase realtime database and futurebuilder

Coming from Firestore, I am a little bit struggling how to receive data from Firebase real time database. I just want a nice grid view of images which are loaded from the realtime database.
Error: flutter: The following NoSuchMethodError was thrown building:
flutter: Class 'DataSnapshot' has no instance method '[]'.
flutter: Receiver: Instance of 'DataSnapshot'
I guess it's index related. No idea how to correctly map it within a list.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16.0),
child: new FutureBuilder(
future: FirebaseDatabase.instance
.reference()
.child('messages')
.child('1551276762582')
.orderByChild('messagetype')
.equalTo('1')
.once(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
if (snapshot.data != null) {
return new Column(
children: <Widget>[
new Expanded(
child: new GridView.builder(
// itemCount: item.length,
gridDelegate:
new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2),
itemBuilder: (context, index) {
return GridTile(
child: CachedNetworkImage(
imageUrl: snapshot.data[index]['imageUrl']
.toString()));
},
),
)
],
);
} else {
return new CircularProgressIndicator();
}
} else {
return new CircularProgressIndicator();
}
}));
}
}
I could solve it with the following code. Again, I have to say that the Firebase documentation really lacks, which is quite disappointing, since Firebase is a great tool. Moreover, I do not understand, that there is no documentation on 'How to use Firebase with Flutter' (we are talking about both Google products.) Notwithstanding, here is the working code for anyone, who likes to use Streambuilder with Gridview.builder with the Realtime Database in Flutter:
StreamBuilder(
stream: FirebaseDatabase.instance
.reference()
.child('messages')
.child(groupId)
.orderByChild('messagetype')
.equalTo(1)
.onValue,
builder: (BuildContext context, AsyncSnapshot<Event> snapshot) {
if (snapshot.hasData) {
if (snapshot.data.snapshot.value != null) {
Map<dynamic, dynamic> map = snapshot.data.snapshot.value;
List<dynamic> list = map.values.toList()
..sort(
(a, b) => b['timestamp'].compareTo(a['timestamp']));
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemCount: list.length,
padding: EdgeInsets.all(2.0),
itemBuilder: (BuildContext context, int index) {
return Container(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(
imageUrl: list[index]["imageUrl"])),
);
},
child: CachedNetworkImage(
imageUrl: list[index]["imageUrl"],
fit: BoxFit.cover,
),
),
padding: EdgeInsets.all(2.0),
);
},
);
} else {
return Container(
child: Center(
child: Text(
'Es wurden noch keine Fotos im Chat gepostet.',
style: TextStyle(fontSize: 20.0, color: Colors.grey),
textAlign: TextAlign.center,
)));
}
} else {
return CircularProgressIndicator();
}
})),
Something that I do that helps me solve issues, is by explicitly turning snapshots into Maps the following way.
Map yourModel = Map.from(datasnapshot);
also many times when handling null data or so on I have to turn the asyncSnap value that comes from the future Builder into a Datasnapshot from firebase in the following way
Datasnapshot snap = asyncSnap.data;
then handle for snap being null
if(snap.value!=null){}
Hope this helps! if you need help send me a message

Resources