Flutter Firebase RealTime Databasee not ordering properly with OrderByChild() [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: Items in StreamBuilder(Using firebase realtime database) are sorted randomly [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);

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.

Saving a value as a string with Flutter Firestore Firebase

I want to save a value from my Cloud Firestore as a string. I am using Flutter with Dart. I have been able to save it when building the page using MaterialepageRoute:
MaterialPageRoute(
builder: (context) => MainScreen(
currentUserId: firebaseUser.uid,
currentUserGender: document['gender'],
currentUserPreference: document['preference'],
)),
But this isn't an option with all of my pages, so I have to look for something else. I want to get the value from my Firestore Database, and then save it as a string, since I want to:
if (currentUserGender == 'male') {
//then do something
}
I have no idea how to do this, I have thought about using a Class, maybe the "get"-function with Firebase, but none have worked. I am not really sure how to do this, so any help is appreciated. I am able to get the currentUser. Here is a picture of my database:
https://imgur.com/KL7HX6P
Thanks in advance.
A Minimal Example: To fetch a Single Document Fields. Swap Collection & Document name in the code with your Own Names.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class GetUser extends StatefulWidget {
#override
_GetUserState createState() => _GetUserState();
}
class _GetUserState extends State<GetUser> {
Map<String, dynamic> userDetails = {};
Future<Null> getUser() async {
await Firestore.instance
.collection('users') // Your Collections Name
.document('eMAE4XF9cTYS12MpfOuWBW4P2WH3') // Your user Document Name
.get()
.then((val) {
userDetails.addAll(val.data);
}).whenComplete(() {
print('Data Fetched');
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
textColor: Colors.white,
color: Theme.of(context).accentColor,
onPressed: () async {
await getUser();
},
child: Text('Get User Detail from Cloud'),
),
userDetails.length > 0
? Column(
children: <Widget>[
Text('${userDetails['gender']}'),
Text('${userDetails['id']}'),
Text('${userDetails['nickname']}'),
userDetails['gender'] == 'male'
? Text('Its Boy')
: Text('Girl'),
],
)
: Text('No user Data, Please Fetch'),
],
),
),
);
}
}

Resources