How to sort a list with Firestore Flutter? - firebase

When creating notes, they are displayed in a different order for me. Notes taken from Firestore. Can I sort the list and display data by creation date, from oldest to newest? I am getting data from firestore
list_note_page.dart
Widget build(BuildContext context) {
return ListView.builder(
itemCount: snapshot.data?.docs.length,
itemBuilder: (context, index) {
QueryDocumentSnapshot<Object?> documentSnapshot =
snapshot.data!.docs[index];
return Dismissible(
key: Key(documentSnapshot.id),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
Database().deleteNote(documentSnapshot.id);
},
background: Container(
padding: const EdgeInsets.only(right: 20),
alignment: Alignment.centerRight,
color: Colors.red,
child: const Text(
'Delete',
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
child: ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: ((context) => AddAndEditNotePage(
header: documentSnapshot['name'],
title: documentSnapshot['title'],
date: documentSnapshot['date'],
id: documentSnapshot.id,
)))),
title: Text(documentSnapshot['name']),
subtitle: Text(documentSnapshot['title']),
trailing: Text(documentSnapshot['date'] ?? ''),
));
});
}

I recommend to add an additional field to your documents, perhaps called created_date that will take a timestamp of the time when you push it to Firebase, (unless that date field you have there is the creation date of that document) then you can do:
Firestore.instance
.collection('YOUR_COLLECTION')
.orderBy('created_date').get()
By default, the descending order is false (oldest to newest).
Check this link or this for further reference.

Related

Window with the ability to recover a deleted note from Firestore [duplicate]

This question already has answers here:
How to create UNDO button, flutter firebase
(2 answers)
Firebase - right way to implement an undoable .update()?
(1 answer)
Closed 12 months ago.
My data is displayed from the Firestore. When I delete a note, I need a window to appear at the bottom where it is suggested to restore the deleted note.
Is it possible to implement this with Firestore? If so, how?
notes_list_page.dart
body: StreamBuilder<QuerySnapshot>(
stream: Database().getMainCollection().snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: snapshot.data?.docs.length,
itemBuilder: (context, index) {
QueryDocumentSnapshot<Object?> documentSnapshot =
snapshot.data!.docs[index];
return Dismissible(
key: Key(documentSnapshot.id),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
Database().deleteNote(documentSnapshot.id);
},
background: Container(
padding: const EdgeInsets.only(right: 20),
alignment: Alignment.centerRight,
color: Colors.red,
child: const Text(
'Delete',
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
child: ListTile(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: ((context) => AddAndEditNotePage(
header: documentSnapshot['name'],
title: documentSnapshot['title'],
date: documentSnapshot['date'],
id: documentSnapshot.id,
)))),
title: Text(documentSnapshot['name']),
subtitle: Text(documentSnapshot['title']),
trailing: Text(documentSnapshot['date'] ?? ''),
));
});
},
),

Flutter - list duplicates old and new values

I have a method for receiving push from Firebase Cloud Messaging, and within it, the notification is allocated within a list. What happens is that this method is making the old value a duplicate of the new value.
Firebase method:
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
if (message.notification != null) {
widget._localNotificationList.title = message.notification.title;
widget._localNotificationList.body = message.notification.body;
widget._pushDecode.notifList.add(widget._localNotificationList);
savePush(); //this method maintains notification on the user's screen, with sharedPreferences
setState(() {});
}
});
Page View:
ListView.builder(
itemCount: widget._pushDecode.notifList.length,
itemBuilder: (context, i) {
return Card(
margin: EdgeInsets.all(10),
elevation: 4,
child: ListTile(
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => removePush(i),
),
title: Text(
widget._pushDecode.notifList.reversed.elementAt(i).title,
),
subtitle: Text(
widget._pushDecode.notifList.reversed.elementAt(i).body,
),
),
);
},
),
You need to use the key value because it does not know if the widget that was in the position you are adding it for example is different then the previous, but using keys he can always know that two widgets are different when they are the same type like your card.
ListView.builder(
itemCount: widget._pushDecode.notifList.length,
itemBuilder: (context, i) {
return Card(
key: UniqueKey(), // you can also use ValueKey('this must be unique for each element in the list.')
margin: EdgeInsets.all(10),
elevation: 4,
child: ListTile(
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => removePush(i),
),
title: Text(
widget._pushDecode.notifList.reversed.elementAt(i).title,
),
subtitle: Text(
widget._pushDecode.notifList.reversed.elementAt(i).body,
),
),
);
},
),

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.

Cant get StreamBuilder to display data from cloud firestore

I know I have a connection to the database and no errors are appearing so I'm pretty confused. The title and code should summarize the problem fairly well. Think I'm missing something?
here is the main code that should be displaying cards with titles from firebase
mainList() {
StreamBuilder(
stream: Firestore.instance.collection('Events').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text('Loading');
} else {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot userPost = snapshot.data.documents[index];
return Container(
width: MediaQuery.of(context).size.width,
height: 350.0,
child: Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Material(
elevation: 14.0,
shadowColor: Color(0x802196F3),
child: Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: 200.0,
child: Text(
'${userPost['title']}',
))
],
),
),
))),
);
},
);
}
});
}
and here is where the function is called:
lass MyAppmain extends State<MyApp> {
#override
Widget build(BuildContext context) {
var listView = ListView.builder(
itemCount: local.length,
itemBuilder: (BuildContext cnxt, int index) {
return new Text(local[index]);
});
return MaterialApp(
home: PageView(
controller: controller,
children: <Widget>[
//home page---------------------------
Scaffold(
appBar: AppBar(
title: Text(
'Events',
),
elevation: 20,
),
//main list view for the cards
//think I use streambuilder for this so google before starting
body: mainList(),//RIGHT HERE
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(context, NewEventTransition());
},
mini: true,
),
),
//Profile Page-------------------------------
Scaffold(
appBar: AppBar(
title: Text(
'Profile',
),
elevation: 20,
),
),
],
));
}
}
Want a listview of cards holding the titles from firebase (will soon be more than titles but want to get this working first)
This is a common problem.
return ListView.builder(
itemCount: snapshot.data.documents.length, // this line is the culprit!
itemBuilder: (context, index) {
print(snapshot.data.documents.length); // it will print null
.......
}
See, It takes some time to fetch data from firebase. When ListView.builder is called the value of snapshot.data.documents.length is actually null. Tho after few seconds it gets data but till then ListView had built the UI and that's why it's blank. To check the value, you can add a Print statement like shown above.
Now there are few ways to solve this problem:
Make an int variable say totalLength, make a function say setTotalLength which makes a call to Firebase/Firestore database and use setState to assign this value to totalLength and then change that code to this:
itemCount: totalLength,
You should Call setTotalLength in your initState method.
Or, you can change your code to this, But I'm NOT 100% sure that this will work:
itemCount: snapshot.data.documents.length ?? 0 // returns 0 if the value is null, may reload UI again when data comes

I need field data to be updated in the UI when there is some update in that field from firestore

I want to update document field value in the UI of flutter whenever there is some change in field value in realtime.
I have tried using StreamBuilder but the only output I am getting is 'Instance QuerySnapshot'
StreamBuilder(
stream: db.collection('users').snapshots(),
initialData: 0,
builder:(BuildContext context, AsyncSnapshot snapshot) {
return new Text(snapshot.data.DocumentSnapshot,
style: TextStyle(
color: Colors.yellow,
fontWeight: FontWeight.bold,
fontSize: 12.0));
},
),`
Expected output is int value of reward field in document uid.
Because of this line stream: db.collection('users').snapshots(),
You are getting the collection, but you expected the document. Refer to the following:
StreamBuilder(
stream: db.collection('users').document(userId).snapshots(), // insert the userId
initialData: 0,
builder:(BuildContext context, DocumentSnapshot snapshot) { // change to DocumentSnapshot instead of AsyncSnapshot
return new Text(snapshot.data.documentID, // you can get the documentID hear
style: TextStyle(
color: Colors.yellow,
fontWeight: FontWeight.bold,
fontSize: 12.0));
},
),`
I have done one of my project with stream builder and it's working fine. I am putting some code snippet from there please check it out this may helps you.
Code of StreamBuilder
StreamBuilder<QuerySnapshot>(
stream: db.collection("students").snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text("There is no expense");
return Expanded(
child: new ListView(
children: generateStudentList(snapshot),
),
);
},
),
code of generateStudentList
generateStudentList(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.documents
.map<Widget>(
(doc) => new ListTile(
title: new Text(doc["name"]),
subtitle: new Text(
doc["age"].toString(),
),
trailing: Container(
width: 100,
child: Row(
children: <Widget>[
IconButton(
onPressed: () {
setState(() {
_studentNameController.text = doc["name"];
_studentAgeController.text = doc["age"].toString();
docIdToUpdate = doc.documentID;
isUpdate = true;
});
},
icon: Icon(
Icons.edit,
color: Colors.blue,
),
),
IconButton(
onPressed: () {
deleteStudent(doc);
},
icon: Icon(
Icons.delete,
color: Colors.red,
),
)
],
),
),
),
)
.toList();
}
You can change fields according your needs.

Resources