Firebase Realtime Database Ordering by Keys - flutter

I want to sort data in Firebase realtime database.
I am using timestamp as key when saving data and I want to sort data by timestamps. I used below code for this purpose.
Widget buildList(ChatUser chatUser) {
return Flexible(
child: StreamBuilder(
stream: _service
.getMessages(chatUser.uid!)
.orderByKey()
.onValue,
builder: (context, snapshot) {
List<ChatMessage> messageList = [];
if (snapshot.hasData) {
final myMessages = Map<dynamic, dynamic>.from(
(snapshot.data as DatabaseEvent).snapshot.value
as Map<dynamic, dynamic>);
myMessages.forEach((key, value) {
final currentMessage = Map<String, dynamic>.from(value);
final message = ChatMessage().fromJson(currentMessage);
messageList.add(message);
});
if (messageList.isNotEmpty) {
return ListView.builder(
padding: const EdgeInsets.all(10),
reverse: true,
itemCount: messageList.length,
controller: scrollController,
itemBuilder: (context, index) {
return buildItem(index, messageList[index], chatUser);
});
} else {
return const Center(
child: Text('Henüz Mesaj yok.'),
);
}
} else {
return const Center(
child: CircularProgressIndicator(
color: Colors.red,
),
);
}
}));
}
As a result, data does not come according to key values, it comes in different orders.
Any suggestions ? Thanks.

The problem is in how you process the results here:
if (snapshot.hasData) {
final myMessages = Map<dynamic, dynamic>.from(
(snapshot.data as DatabaseEvent).snapshot.value
as Map<dynamic, dynamic>);
myMessages.forEach((key, value) {
final currentMessage = Map<String, dynamic>.from(value);
final message = ChatMessage().fromJson(currentMessage);
messageList.add(message);
});
The order of the keys inside a Map is by definition undefined. So when you call (snapshot.data as DatabaseEvent).snapshot.value as Map<dynamic, dynamic>), you're actually dropping all ordering information that the database returns.
To process the results in the correct order, iterate over the children of the snapshot, and only then convert each child to a Map.

Complementing Frank, try to assign snapshot to a List of snapshots using List snapshotList = xxx.children.toList(); If you do something like snapshotList[i].value you will notice that the key is not present, the solution to get it back is to use the get .key.
You can see bellow an exemple how I did to solve the same problem in my project.
final List<DataSnapshot> snapshotList = snapshot.data.children.toList();
final List<Map> commentsList = [];
for (var i in snapshotList) {
Map<String?, Map> comment = {i.key: i.value as Map};
commentsList.add(comment);
}
In the code above, commentsList will get you a list of Maps ordered according to original database.
I hope this help. If anyone has a more straightforward solution, please, share with us.

Related

firestore doesnt show documents even though they are available

I have following code to add data to firebasefirestore
Future<void> sendMessage({
required String msg,
required String id,
}) async {
var docId = getDocId(id); // returns sth like "AbcDe-FghiJ"
DocumentReference documentReferencer = chat.doc(docId).collection('chatMsg').doc();
Map<String, dynamic> data = <String, dynamic>{
"message": msg,
"sentBy": ownId,
"sentAt": DateFormat('yyyy-MM-dd – kk:mm:ss').format(DateTime.now())
};
await documentReferencer.set(data);
}
I used following code to get the data
StreamBuilder<QuerySnapshot>(
stream: firebaseInstance.collection('Messages').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError || !snapshot.hasData) {
return const Center(
child: CircularProgressIndicator()
);
} else {
var data = snapshot.data.docs;
return listBuilder(data);
}
}
)
listBuilder(listData) {
return ListView.builder(
shrinkWrap: true,
itemCount: listData.length,
itemBuilder: (BuildContext context, int index) {
return Text(listData[index].id);
}
)
}
However, data show 0 items even though there is a document present.
My question is how can I get the list of documents from Messages?
I was having the same exact problem with subcollections on Firestore and even asked a question here to get some help over it. Though, it seems like the snapshots won't show the documents having a subcollection in them as there is no field inside any of them. So what I did to counter this was to just add anything (just a random variable) and then it was able to find the documents.
This is my current layout:
I've just added another line of code to just add this whenever I'm inserting a new subcollection.
collection
.set({
'dummy': 'data'
})
.then((_) => print('Added'))
.catchError((error) => print('Add failed: $error'));

How can I get data from various nodes in a Realtime-database in Flutter?

I'm trying to display data from a real-time database in my widget, such as a picture, a name, or a message, but I'm not sure how to achieve it from several nodes. Thank you in advance for your assistance.
For add data :
List lists = [];
stream to get data :
final dbRef = FirebaseDatabase.instance
.ref()
.child("chatList")
.child("D1NilPUI6PY0jSA1tk0wRzi6FsO2");
Widget to show data :
_widget() {
return StreamBuilder(
stream: dbRef.onValue,
builder: (BuildContext context, AsyncSnapshot 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({"chatList": index, ...data}));
print("DATA : $item");
if (snap.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else {
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: item.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(item[index]['content'].toString()),
);
},
);
}
} else {
return const Center(child: Text("No data"));
}
},
);
}
Table structure Images :
Image 1 :
Image 2 :
This code:
final dbRef = FirebaseDatabase.instance
.ref()
.child("chatList")
.child("D1NilPUI6PY0jSA1tk0wRzi6FsO2");
This refers to a node /chatList/D1NilPUI6PY0jSA1tk0wRzi6FsO2 in your database. Since the screenshot doesn't show any data under that exact path, you will get a snapshot without any value from reading it.
If you want to read all nodes under /chatList, you can use that path in the query, and then loop over all the children of the snapshot.
final dbRef = FirebaseDatabase.instance
.ref()
.child("chatList");
dbRef.onValue.listen((event) => {
event.snapshot.children.forEach((child) {
print(child.key);
})
})
Since you have two levels with dynamic keys under chatList, you'll have to use two nested loops to get to the named properties:
final dbRef = FirebaseDatabase.instance
.ref()
.child("chatList");
dbRef.onValue.listen((event) => {
event.snapshot.children.forEach((child) {
print(child.key);
child.children.forEach((child2) {
print(child2.key);
print(child2.child("lastMessage/content").value);
})
})
})

Future builder returns null although my list is not empty

I have this future builder which loads a list of movies in my provider class. Whenever I reload my screen, the movies do not get returned. Below is the future builder
FutureBuilder(
future: movieData.getTrendingMovies(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasData) {
return Swiper(
itemBuilder: (BuildContext context, i) {
return ChangeNotifierProvider(
create: (context) => Movie(),
child: MovieContainer(
imageUrl: movieData.movies[i].imageUrl,
id: movieData.movies[i].id,
rate: movieData.movies[i].rate,
title: movieData.movies[i].title,
),
);
},
itemCount: movieData.movies.length,
viewportFraction: 0.25,
scale: 0.4,
);
} else {
return Text(snapshot.error.toString()); // it returns null on the screen
}
}),
Also in my homescreen where I display my movies, after the build method, I create a listener(moviesData) to listen to all changes in the movies provider.
final movieData = Provider.of<Movies>(context, listen: false);
Below is also the methos which fetches the movies from a restfulAPI using http get request
Future<void> getTrendingMovies() async {
List<String> movieTitles = [];
List<String> movieImageUrls = [];
List<String> movieDescriptions = [];
List<String> movieReleaseDates = [];
List<String> movieRates = [];
List<String> movieIds = [];
const _apiKey = '******************************';
const url =
'https://api.themoviedb.org/3/trending/all/week?api_key=$_apiKey';
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode >= 400) {
print(response.statusCode);
return;
}
final extractedData = json.decode(response.body);
List moviesList = extractedData['results'] as List;
List<Movie> loadedMovies = [];
for (int i = 0; i < moviesList.length; i++) {
String movieTitle = moviesList[i]['original_title'] ?? '';
String? movieImage =
'https://image.tmdb.org/t/p/w400${moviesList[i]['poster_path']}'; //results[0].poster_path
String movieDescription =
moviesList[i]['overview'] ?? ''; //results[0].overview
String movieReleaseDate = moviesList[i]['release_date'] ?? '';
String? movieRate = moviesList[i]['vote_average'].toString();
String? movieId = moviesList[i]['id'].toString();
movieTitles.add(movieTitle);
movieImageUrls.add(movieImage);
movieDescriptions.add(movieDescription);
movieReleaseDates.add(movieReleaseDate);
movieRates.add(movieRate);
movieIds.add(movieId);
loadedMovies.add(
Movie(
id: movieIds[i],
title: movieTitles[i],
imageUrl: movieImageUrls[i],
description: movieDescriptions[i],
rate: double.parse(movieRates[i]),
releaseDate: movieReleaseDates[i],
),
);
}
_movies = loadedMovies;
notifyListeners();
//print(_movies.last.title); //This prints the name of the last movie perfectly....This gets called unlimited times whenever I set the listen of the **moviesData** to true
} catch (error) {
print(error);
}
}
There's a couple of things to unpack here.
Instead of a ChangeNotifierProvider, I believe you should use a Consumer widget that listens to your Movies provided service when you call the notifyListeners call, so make it Consumer<Movie>.
You can still call it using the Provider.of above for the sake of making the async call via the FutureBuilder, but I believe because you're not returning anything out of the getTrendingMovies and is just a Future<void> and you're querying the snapshot.hasData, well there is no data coming through the snapshot. Maybe instead you should call snapshot.connectionState == ConnectionState.done as opposed to querying for whether it has data.
Make sure that the response.body is truly returning a JSON value, but I believe your issue is in one of the points above.

How can I convert FutureBuilder code to StreamBuilder?

I am trying to get data from Firestore and pass that data to screen using stream. I have done this using FutureBuilder, this solution works as followed, but I need to use StreamBuilder Can anyone help me find the problem?
Future<List<Business>> list(FirebaseFirestore _firesore) async {
CollectionReference _col = _firesore.collection('Buisiness');
var _result = await _col.get();
var _docs = _result.docs;
return List.generate(_docs.length, (index) {
var satir = _docs[index].data();
return Business.fromMap(satir as Map<String, dynamic>);
});
}
This Code works in FutureBuilder but not StreamBuilder
StreamBuilder<List<Business>>(
stream: _firestorelist.list(_firestore), // Error Here
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Business>? data = snapshot.data;
return ListView.builder(
itemCount: data!.length,
itemBuilder: (context, index) {
var result = data[index];
return ListTile(
title: Text(result.nereden),
subtitle: Text(result.nereye),
trailing: Text(result.fiyat),
);
},
);
} else {
return CircularProgressIndicator();
}
},
)```
You can write your data source method as
Stream<List<Business>> list(FirebaseFirestore _firesore) {
CollectionReference _col = _firesore.collection('Buisiness');
final _snap = _col.snapshots();
return _snap.map((event) => event.docs
.map<Business>((e) => Business.fromMap(e.data() as Map<String, dynamic>))
.toList());
}
The current method is a One-time Read method, You can get snapshots from the specific collection.
You can change the method like this and Then use it as stream in streamBuilder:
list(FirebaseFirestore _firesore) async {
CollectionReference _col = _firesore.collection('Buisiness');
var _result = await _col.snapshots();
return _result;
}

unable to use .toList with Data from MongoDB using flutter

Sorry if it's a stupid question I am beginner in Flutter and MongoDB Here is my code to return collection data btw this is the only time I use Mongo_Dart all other operations done using JS on heroku
class Azkar {
getAzkar() async {
var db = await Db.create(
'mongodb+srv://Adham:<password>#cluster0.nm0lg.mongodb.net/<db>retryWrites=true&w=majority');
await db.open();
print('Connected to database');
DbCollection coll = db.collection('zekrs');
return await coll.find().toList();
}
}
It is working and I am able to print returned data from another class it is List<Map<String, dynamic>> I want to know how should I use it to generate ListTile with all data.
This package is not worth it. I solved this issue by moving out this part of code on the backend side (NodeJS) in the cloud and just getting what I need with an HTTP request.
Instead of returning data in List<Map<String, dynamic>>, create a class for your data. Suppose your data gives us a list of users. Then
class User {
User({
this.id,
this.name,
});
int id;
String name;
}
This would be your Azkar class
class Azkar {
getAzkar() async {
final db = await Db.create(
'mongodb+srv://Adham:<password>#cluster0.nm0lg.mongodb.net/<db>retryWrites=true&w=majority');
await db.open();
print('Connected to database');
final coll = db.collection('zekrs');
final zekrsList = await coll.find().toList();
List<User> users = [];
for (var item in zekrsList) {
final user = User(
id: item['id'],
name: item['name'],
);
users.add(user);
}
return users;
}
}
You should do something like this.
FutureBuilder(
future: getAzkar(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.all(8),
child: Column(
children: [
Text("Name = ${snapshot.data[index].name}"),
Text("Id = ${snapshot.data[index].id}"),
],
),
);
});
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
if anyone still have this issue,
I solved it by setting this:
final zekrsList = await coll.find().toList();
to
final zekrsList = await coll.find(where.sortBy('_id')).toList();