Flutter: Items in StreamBuilder(Using firebase realtime database) are sorted randomly [duplicate] - firebase

This question already has an answer here:
Flutter: Firebase Real-Time database orderByChild has no impact on query result
(1 answer)
Closed 2 years ago.
I'm creating a simple application with Firebase Realtime database where a user inputs a text and it gets added to a list of chats.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _firebaseRef = FirebaseDatabase().reference().child('chats');
TextEditingController _txtCtrl = TextEditingController();
#override
Widget build(BuildContext context) {
var comments = _firebaseRef.orderByChild('time').limitToLast(10);
return Scaffold(
body: Container(
child: SafeArea(
child: Column(
children: <Widget>[
Container(
child: Row(children: <Widget>[
Expanded(child: TextField(controller: _txtCtrl)),
SizedBox(
width: 80,
child: OutlineButton(
child: Text("Add"),
onPressed: () {
sendMessage();
}))
])),
StreamBuilder(
stream: comments.onValue,
builder: (context, snap) {
if (snap.hasData &&
!snap.hasError &&
snap.data.snapshot.value != null) {
Map data = snap.data.snapshot.value;
List item = [];
data.forEach(
(index, data) => item.add({"key": index, ...data}));
return Expanded(
child: ListView.builder(
itemCount: item.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(item[index]['message']),
);
},
),
);
} else
return Center(child: Text("No data"));
},
),
],
),
),
),
);
}
sendMessage() {
_firebaseRef.push().set({
"message": _txtCtrl.text,
'time': DateTime.now().millisecondsSinceEpoch
});
}
}
It stores and retrieves data perfectly. But when I try adding data, the new items are placed at random points in the list.
For example, in the image below, the last item I placed into the list was 'Nine'. But it was put in the center of the list:
I've tried sorting the list by timestamps, but it did nothing.
What could be causing this issue? And how can I fix it?

When you call snap.data.snapshot.value; the data in the snapshot (which is ordered) is converted to a Map<String, Object> which isn't ordered. To maintain the order, you'll want to listen to onChild... instead.
Note that FlutterFire has a convenient firebase_list library that handles most of the heavy lifting of onChild... for you.
Also see:
Flutter Firebase Database wrong timestamp order
Flutter sort Firebase snapshot by timestamp
Flutter: Firebase Real-Time database orderByChild has no impact on query result

This might work:
use a Query
Query comments = _firebaseRef.orderByChild('time').limitToLast(10);

Related

I am trying to make a grocery app using flutter and firebase, everything is working but when I press the checkbox it Checks all of them

I made a floatingactionbutton and every time you press it it adds an item, and each item has a checkbox next to it but when I check off one item it checks all of them, I've spent a lot of time trying to figure out how to fix this but I can't. I could really use your help.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(FireApp());
}
class FireApp extends StatefulWidget {
#override
_FireAppState createState() => _FireAppState();
}
bool isChecked = false;
class _FireAppState extends State<FireApp> {
final TextController = TextEditingController();
#override
Widget build(BuildContext context) {
CollectionReference groceries =
FirebaseFirestore.instance.collection('groceries');
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: TextField(
controller: TextController,
),
),
body: Center(
child: StreamBuilder(
stream: groceries.orderBy('name').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
return ListView(
children: snapshot.data!.docs.map((grocery) {
return Center(
child: Row(
children: [
Container(color: Colors.red,height: 50,child: Text(grocery['name'])),
Checkbox(
materialTapTargetSize: MaterialTapTargetSize.padded,
value: isChecked,
activeColor: Colors.black,
checkColor: Colors.greenAccent,
onChanged: (bool) {
setState(() {
isChecked = !isChecked;
});
}
)],
),
);
}).toList(),
);
},
),
),
floatingActionButton: FloatingActionButton(onPressed: () {
groceries.add({
'name': TextController.text,
});
},),
),
);
}
}
You are using the same variable for all your checkboxes (isChecked) but you ougth to have one per data, you could add that attribute to your firebase document so its synced or you could create it locally but each time your stream updates you will need to compare what grocery correspond to a checkbox value which can be hard.
UPDATE
The easiest way is to have a bool parameter in your Firestore document
Then just push an update any time the user tap
return ListView(
children: snapshot.data!.docs.map((grocery) {
return Center(
child: Row(
children: [
Container(color: Colors.red,height: 50,child: Text(grocery['name'])),
Checkbox(
materialTapTargetSize: MaterialTapTargetSize.padded,
value: grocery['checked'],
activeColor: Colors.black,
checkColor: Colors.greenAccent,
onChanged: (val) async {
final data = grocery.data();
data['checked'] = val;
await grocery.reference.update(data);
}
)],
),
);
}).toList(),
);
For now this is sufficient to answer your question, you will see later that this incurs in more Firestore calls, unnecesary rebuild of all widgets in the list and so on and you will have to think another way to optimize resources, like watching the stream somewhere else to have a local List of bools that keeps in sync all values of the groceries so you only update locally with an setState and once in the cloud at the end (a save button perhaps)

Flutter/Firestore/Provider - Error shown for split second then stream displayed, how can I load stream values on startup?

I am using a Stream Provider to access Firestore data and pass it around my app. The problem I am facing starts when I first run the app. Everything starts as normal but as I navigate to the screen where I am using the Stream values in a list view, I initially get an error before the UI rebuilds and the list items appear after a split second. This is the error I get:
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building OurInboxPage(dirty, dependencies: [_InheritedProviderScope<List<InboxItem>>]):
The getter 'length' was called on null.
Receiver: null
Tried calling: length
I'm guessing this has something to do with the load time to access the values and add them to the screen? How can I load all stream values when the app starts up to avoid this?
Here is my Stream code:
Stream<List<InboxItem>> get inboxitems {
return orderCollection
.where("sendTo", isEqualTo: FirebaseAuth.instance.currentUser.email)
.snapshots()
.map(
(QuerySnapshot querySnapshot) => querySnapshot.docs
.map(
(document) => InboxItem.fromFirestore(document),
)
.toList(),
);
}
I then add this to my list of Providers:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
StreamProvider<List<InboxItem>>.value(value: OurDatabase().inboxitems),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<OurUser>(
builder: (_, user, __) {
return MaterialApp(
title: 'My App',
theme: OurTheme().buildTheme(),
home: HomepageNavigator(),
);
},
);
}
}
And finally the page I want to display the stream items:
class OurInboxPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
List<InboxItem> inboxList = Provider.of<List<InboxItem>>(context);
return Scaffold(
body: Center(
child: ListView.builder(
itemCount: inboxList.length,
itemBuilder: (context, index) {
final InboxItem document = inboxList[index];
return Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(document.event),
Icon(Icons.arrow_forward_ios)
],
),
);
},
),
),
);
}
}
Thanks
Yeah its trying to build before the data is populated, hence the null error.
Wrap your ListView.builder in a StreamBuilder and having it show a loading indicator if there's no data.
StreamBuilder<List<InboxItem>>(
stream: // your stream here
builder: (context, snapshot) {
if (snapshot.hasData) {
return // your ListView here
} else {
return CircularProgressIndicator();
}
},
);
I'm assuming your not using the latest version of provider because the latest version requires StreamProvider to set initialData.
If you really want to use StreamProvider and don't want a null value, just set its initialData property.
FROM:
StreamProvider<List<InboxItem>>.value(value: OurDatabase().inboxitems),
TO:
StreamProvider<List<InboxItem>>.value(
value: OurDatabase().inboxitems,
initialData: <InboxItem>[], // <<<<< THIS ONE
),
If you want to display some progress indicator while getter function inboxitems is executed initially. You don't need to modify the StreamProvider, and just add a null checking in your OurInboxPage widget.
class OurInboxPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final List<InboxItem>? inboxList =
Provider.of<List<InboxItem>?>(context, listen: false);
return Scaffold(
body: inboxList == null
? const CircularProgressIndicator()
: ListView.builder(
itemCount: inboxList.length,
itemBuilder: (_, __) => Container(
height: 100,
color: Colors.red,
),
),
);
}
}
There are 2 ways to solve the issue.
Use the progress bar while the data is loading.
StreamBuilder<int>(
stream: getStream(),
builder: (_, snapshot) {
if (snapshot.hasError) {
return Text('${snapshot.error}');
} else if (snapshot.hasData) {
return Text('${snapshot.data}');
}
return Center(child: CircularProgressIndicator()); // <-- Use Progress bar
},
)
Provide dummy data initially.
StreamBuilder<int>(
initialData: 0, // <-- Give dummy data
stream: getStream(),
builder: (_, snapshot) {
if (snapshot.hasError) return Text('${snapshot.error}');
return Text('${snapshot.data}');
},
)
Here, getStream() return Stream<int>.

Flutter Firebase RealTime Databasee not ordering properly with OrderByChild() [duplicate]

This question already has an answer here:
Flutter: Firebase Real-Time database orderByChild has no impact on query result
(1 answer)
Closed 2 years ago.
I'm creating a simple application with Firebase Realtime database where a user inputs a text and it gets added to a list of chats.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _firebaseRef = FirebaseDatabase().reference().child('chats');
TextEditingController _txtCtrl = TextEditingController();
#override
Widget build(BuildContext context) {
var comments = _firebaseRef.orderByChild('time').limitToLast(10);
return Scaffold(
body: Container(
child: SafeArea(
child: Column(
children: <Widget>[
Container(
child: Row(children: <Widget>[
Expanded(child: TextField(controller: _txtCtrl)),
SizedBox(
width: 80,
child: OutlineButton(
child: Text("Add"),
onPressed: () {
sendMessage();
}))
])),
StreamBuilder(
stream: comments.onValue,
builder: (context, snap) {
if (snap.hasData &&
!snap.hasError &&
snap.data.snapshot.value != null) {
Map data = snap.data.snapshot.value;
List item = [];
data.forEach(
(index, data) => item.add({"key": index, ...data}));
return Expanded(
child: ListView.builder(
itemCount: item.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(item[index]['message']),
);
},
),
);
} else
return Center(child: Text("No data"));
},
),
],
),
),
),
);
}
sendMessage() {
_firebaseRef.push().set({
"message": _txtCtrl.text,
'time': DateTime.now().millisecondsSinceEpoch
});
}
}
It stores and retrieves data perfectly. But when I try adding data, the new items are placed at random points in the list.
For example, in the image below, the last item I placed into the list was 'Nine'. But it was put in the center of the list:
I've tried sorting the list by timestamps, but it did nothing.
What could be causing this issue? And how can I fix it?
When you call snap.data.snapshot.value; the data in the snapshot (which is ordered) is converted to a Map<String, Object> which isn't ordered. To maintain the order, you'll want to listen to onChild... instead.
Note that FlutterFire has a convenient firebase_list library that handles most of the heavy lifting of onChild... for you.
Also see:
Flutter Firebase Database wrong timestamp order
Flutter sort Firebase snapshot by timestamp
Flutter: Firebase Real-Time database orderByChild has no impact on query result
This might work:
use a Query
Query comments = _firebaseRef.orderByChild('time').limitToLast(10);

Collection per Users FireStore Flutter App

Im trying to create a collection of users in my flutter application.
The application has a stock collection with items which are documents and those documents, like plates or bags, has 2 fields
I create a root users collection and that collection documents which identified the used with their user ID, one document for each user ID (the one you get from FirebaseAuth).
Ive been told that I have to set the access rules in Firebase to allow to each user to have their own stock collection.
Github https://github.com/juancarlosjr97/flutter_ims
Each user should have their own collection
My database the own it works looks like this:
and the other collection with a collection of users looks like this:
This is my code where I get access to the stock collection
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class StockWidget extends StatefulWidget {
#override
StockWidgetApp createState() => new StockWidgetApp();
}
class StockWidgetApp extends State<StockWidget> {
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('stock').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.item),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.item),
trailing: Text(record.instock.toString()),
onTap: () =>
record.reference.updateData({'instock': record.instock + 1}),
onLongPress: () =>
record.reference.updateData({'instock': record.instock - 1}),
),
),
);
}
}
class Record {
final String item;
final int instock;
final DocumentReference reference;
#override
String toString() => "Record<$item:$instock>";
Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['item'] != null),
assert(map['instock'] != null),
item = map['item'],
instock = map['instock'];
Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
}
Use this collection for stock :
final userId = [get the user id from auth]
final col = Firestore.instance.collection("users").document(userId).collection("stock");
//add stock
col.add(yourstock);

Save Bookmark Article in Firebase Flutter

In Flutter app I want to fetch data list
I want to save bookmark any article from article list in Fire store data base but when bookmark button tapped the same article save in the database every time. I want that article should save in database for the first time
Does anyone lead me to the correct way? Any help is highly appreciated!
My code
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() => runApp( MyHomePage());
class MyHomePage extends StatefulWidget {
#override
_MyHomePage createState() => _MyHomePage();
}
class _MyHomePage extends State<MyHomePage> {
String title;
String subtitle;
int id;
Firestore firestore = Firestore.instance;
DocumentSnapshot document;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('jdj'),
),
body: Container(
child: ListView(
children: <Widget>[
stremBuilder(),
Container(
height: 310,
color: Colors.amber,
)
],
),
));
}
Widget stremBuilder() {
return Container(
height: 200,
child: StreamBuilder(
stream: Firestore.instance.collection("User").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: Text("Loding"),
);
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return listItem(context, snapshot.data.documents[index]);
},
);
},
),
);
}
Widget listItem(BuildContext context, DocumentSnapshot document) {
return ListTile(
title: Text(document["title"]),
subtitle: Text(document["subtitle"]),
trailing: GestureDetector(
child: Icon(Icons.bookmark),
onTap: () {
setState(() {
saveData(id, document);
});
}),
);
}
Map<String, dynamic> savedata = {};
saveData(int id, DocumentSnapshot document) {
Map<String, dynamic> savedata = {
"id": id,
"saveTitle": document["title"],
'saveSubtitle': document["subtitle"]
};
Firestore.instance.collection("savedata").add(savedata);
}
}
It looks like on the right track. Checking the code, the tapped List item should be saved. If what you're looking for is to save the "bookmark" only once, and clicking on it again should remove the saved bookmark. Then you can delete the document upon pressing again.
await FirebaseFirestore.instance.collection('savedata').doc(docId).delete();
Make sure to keep track of the id of the document that you'd like to delete to be used as reference.

Resources