Adding New Data in Flutter using Firestore, Result in Redundant Data? - flutter

I was trying to get data from firestore using class defined like this one:
Future<QuerySnapshot<Map<String, dynamic>>> getPubs() async {
queryPubs = await pubsDb.get();
if (queryPubs!.docs.isEmpty) {
log("[getPubs] Variable queryPubs is empty");
} else {
await pubData(queryPubs!.docs).then((value) {
if (value.isNotEmpty) {
isPub.value = true;
log("[getPubs] Variable pubAll is not empty");
} else {
log("[getPubs] Variable pubAll is empty");
}
});
}
return queryPubs!;
}
Future<List<Map<String, dynamic>>> pubData(List<DocumentSnapshot>? docs) async {
for (int i = 0; i < docs!.length; i++) {
DbPublisher dbPublisher = DbPublisher.fstoreInfo(docs[i]);
final imgUrl = await getImgUrl(dbPublisher.imgName, DataType.PUBLISHER);
pubInfo = {
"name": dbPublisher.name,
"imgURL": imgUrl,
"imgName": dbPublisher.imgName,
"desc": dbPublisher.desc,
"email": dbPublisher.email,
"active": dbPublisher.active,
"status": dbPublisher.status,
"albums": dbPublisher.albums,
"songs": dbPublisher.songs,
"state": dbPublisher.state,
"privilege": dbPublisher.privilege,
"payout": dbPublisher.payout,
"compensation": dbPublisher.compensation,
"started": dbPublisher.started,
"due": dbPublisher.due,
"license": dbPublisher.license,
"duration": dbPublisher.duration,
"valid": dbPublisher.valid,
};
pubAll!.add(pubInfo);
}
return pubAll!;
}
I get the data, but when I was about to add one more data; the new data doesn't get displayed. Instead, it was previous data and there were redundant of that. I used the latest package of firestore. Did someone ever experience this one? I used GetX dependency injection for all of this. I used GetxService as it made it as permanent.
Update: redundant here I meant there is the same data over and over. But when I restarted the app, all the data return normal as it used to be. I wonder what is going on?
Update:
The flutter code
Expanded(
child: FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
future: dNd.getPubs(),
builder: (context, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
listAlbs = snapshot.data!.docs;
if (listAlbs.isNotEmpty) {
totalAlbs = listAlbs.length;
log("Load $totalAlbs albums from Firestore");
return ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: const ScrollPhysics(),
controller: scrollCtrl,
itemCount: listAlbs.length,
itemBuilder: (context, index) {
return albsFstore(context, dNd.pubAll![index], index);
},
);
} else {
return const Center(child: FavText("The album data is empty", 15.0));
}
} else if (snapshot.hasError) {
return const Center(child: FavText("The server is error", 15.0));
} else {
return const Center(child: FavText("The data is empty", 15.0));
}
} else if (snapshot.connectionState == ConnectionState.waiting) {
return AlignPositioned(
dx: 0,
dy: -(MediaQuery.of(context).size.height / 4),
child: Container(
width: 128,
height: 128,
padding: const EdgeInsets.all(8.0),
decoration: const BoxDecoration(borderRadius:
BorderRadius.all(Radius.circular(16.0)), color: Color.fromARGB(88, 44, 44, 44)),
child: const LoadingIndicator(indicatorType: Indicator.ballRotateChase, colors: rainbowColors),),);
}
return const SizedBox();
},
),
),

Cloud Firestore gives you the ability to read the value of a collection or a document. This can be a one-time read, or provided by real time updates when the data within a query changes.
For basic write operations, you can use set() to save data to a specified reference, replacing any existing data at that path and to read a collection or document once, call the Query.get or DocumentReference.get methods.
As recommended in comments, you should check these examples once Duplicate Data from Firestoreand How to get data from Firestore.
Also go through this useful document for one time read implementation.

Related

Sort messages in Realtime DB by timestamp

I am having trouble by getting my messages stored in realtime db in order.
Here's my db structure
messages: {
$chatId: {
$messageId: {
timestamp: 1664884736728,
sender:"36a72WVw4weQEoXfk3T9gCtOL9n2",
message: "Hello world"
}
}
}
This is my chat repository
//get all messages of a chat
Query getMessages(String chatId) {
//get all messages of a chat
final messages = _database.ref().child("messages/$chatId");
//return all messages of a chat
return messages;
}
}
and this is how I am displaying it
StreamBuilder(
stream: _chatRepository.getMessages(chatId).onValue,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.data != null &&
snapshot.data?.snapshot?.value != null &&
snapshot.hasData) {
final messages = Map.from(snapshot.data?.snapshot.value as Map);
return ListView.builder(
itemCount: messages.length,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
final currChat = messages.values.toList()[index];
return BubbleCustom(...),
);
},
);
}
return Container();
},
),
This is my index in realTimeDB
{
"rules": {
"messages": {
"$chatId": {
".indexOn": ["timestamp"]
}
}
}
}
I need to get them in timestamp order. Is there any way I can do this? I hope you can help me. Thanks in advance!
You can use orderByChild to get the messages in the order of a specific child. So there that'd be:
stream: _chatRepository.getMessages(chatId).orderByChild('timestamp').onValue
Don't forget to define an index for timestamp, so that the sorting can be done on the database server.
To learn more on this, see the Firebase documentation on sorting and filtering data.
I solved it by doing it in the frontend like this:
final messages = Map.from(snapshot.data?.snapshot.value as Map);
//sort messages by timestamp ascending order
final messagesList = messages.values.toList();
messagesList.sort((a, b) => a["timestamp"].compareTo(b["timestamp"]));
final newMessagelist = List.from(messagesList.reversed);
return ListView.builder(
physics: const BouncingScrollPhysics(),
reverse: true,
itemCount: newMessagelist.length,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
final currChat = newMessagelist[index];
return BubbleCustom(
text: currChat["message"],
isSender: currChat["sender"] == senderUser.id,
tail: true,
textStyle: TextStyle(
fontSize: 16,
color: currChat["sender"] == senderUser.id ? Colors.white : Colors.black,
),
);
},
);
with reverse: true and inverting the list before I render it.

Flutter FutureBuilder does not stop showing CircularProgressIndicator

I am trying to receive data using a FutureBuilder, but it hangs on the CircularProgressIndicator. I think it's remaining on ConnectionState.waiting but I'm not sure why.
#override
initState() {
_widgetList = getWidgetList();
}
Stream<List<String>> getFriendUUIDS() => Firestore.friends
.doc(gameManager.myself.uuid)
.snapshots()
.map((snapshot) => ((snapshot.data()?.keys)?.toList()) ?? []);
Future<List<MomentWidget>> getWidgetList() async{
List<MomentWidget> widgetList = [];
Set<String> momentIds = Set();
await for (final uuids in getFriendUUIDS()){
for (String uuid in uuids){
DocumentSnapshot<Map<String, dynamic>> values = await Firestore.users
.doc(uuid)
.get();
for (String momentId in values.data()?['moments'] ?? [] ){
momentIds.add(momentId);
}
}
}
for (String momentId in momentIds){
DocumentSnapshot<Map<String, dynamic>> values =
await Firestore.instance.collection('moments').doc(momentId).get();
Map<String, dynamic>? data = values.data()!;
String downloadURL = await storage.ref('moments/$momentId').getDownloadURL();
MomentWidget widget = MomentWidget(numLikes: data['liked_by'].length ,
location: data['location'],
date: data['timestamp'],
names: data['taggedFriends'].toString(),
shotBy: data['taken_by'], image: NetworkImage(downloadURL));
widgetList.add(widget);
}
return widgetList;
}
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
height: size.height,
width: size.width,
child: FutureBuilder(
future: _widgetList,
builder: (context, AsyncSnapshot<List<MomentWidget>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemBuilder: (context, pos) {
return snapshot.data![pos];
},
);
}
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
return Text('Unhandled State');
}
}
),
);
}
I have tried to get the Future inside of initState(), and have tried to use snapshot.hasData instead, to no avail.
I have encountered a similar problem. When building an object from json , if the types don't match , it can quietly fail. I do not think your widgetList is ever returned. In my case I had a variable "cost" that I thought would be of type int , however in the database it was of type String. It always quietly failed and never showed the markers on the map widget
So:
Check how many times that loop of yours is executed. Probably only once and then it quietly fails
If the above happens:
Makes sure the types of your variables match the ones from the database. Comment out every variable one by one to find where the problem is.
Let me know if it works

(Flutter - Firestore) The getter 'documents' was called on null

I have stored the download links of images on Firestore while the images are in firebase storage.
I am trying to retrieve the links and display them via stream builder but I'm encountering an error.
What can I do to fix this.
StreamBuilder:
StreamBuilder(
stream: db
.collection("Highway Secondary School Announcements")
.doc()
.snapshots(),
builder: (context, snapshot) {
if (snapshot!= null && snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasError) {
return Center(
child: Text("Snapshot Was Not Retrieved"),
);
}
for (int i = 0; i < snapshot.data.documents; i++) {
listOfUrls.add(snapshot.data.documents[i]['url']);
listOfPics.add(Padding(
padding: const EdgeInsets.only(top: 50, bottom: 50),
child: Image.network(listOfUrls[i]),
));
}
return Container(
child: ListView(
children: listOfPics,
),
);
}),
Error:
The getter 'documents' was called on null.
Receiver: null
Tried calling: documents
If you have doc(), you need to specify which documentation(doc('name')) you want to be read, otherwise, you could remove ".doc()".
Reference:Cloud Firestore
before
db.collection("Highway Secondary School Announcements").doc().snapshots()
after
db.collection("Highway Secondary School Announcements").snapshots()
Second question:
I used "final items = snapshot.data?.docs" to get documents from that snapshot.
Here has a nice example Cloud Firestore flutter
final items = snapshot.data?.docs.reversed;
for ( var item in items!) {
final itemName = item.get('name');
final itemLogo = item.get('logo');
final itemDate = item.get('date');
// String itemDate2 = DateTime.fromMillisecondsSinceEpoch(itemDate).toString();
final itemBubble = _getListItemWidget(
iconName: itemLogo,
titleName: itemName,
subTitleName: DateTime.now(),
scoreKeeper1: scoreKeeper1,
scoreKeeper2: scoreKeeper2,
scoreKeeper3: scoreKeeper3
);
itemBubbles.add(itemBubble);
}

Do Firebase and Flutter support one-time reads on the web?

I'm using Firebase and Flutter to read a List of Objects (EspecieModel). It's working perfect in IOS and Android, however It doesn't work on the Web (an empty List is retrieved).
I'm reading from Firebase as follows ...
Future<List<EspecieModel>> cargarTipoEspecie() async {
final List<EspecieModel> tipoEspecie = [];
Query resp = db.child('PATH/tipoespecie');
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
}
And I'm using a Future Builder to display the information...
FutureBuilder(
future: _tipoEspecieBloc.cargarTipoEspecie(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// print(snapshot.connectionState);
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData{
// print(snapshot.data);
final _especies = snapshot.data;
return Stack(
children: <Widget>[
ListView.builder(
itemCount: _especies!.length,
itemBuilder: (context, i) {
return _crearItem(context, _especies[i], i);
},
),
],
);
} else if (snapshot.hasError) {
print(snapshot.error);
return Text(snapshot.error.toString());
}
else {
return //CircleProgressIndicator Code
);
}
},
),
I can't identify what I'm doing wrong
How to do a one-time Firebase Query that works well on IOS, Android, and also on the Web??
This won't work:
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
The onChildAdded is not part of the await, so I doubt everything waits the way you seem to want. Just adding await in one place, does not make the rest of your code synchronous.
Instead consider using just once() and then populating your tipoEspecie array by looping over snapshot.value.values (a relatively new addition to the API).
var snapshot = await resp.once();
snapshot.value.values.forEach((node) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(node.value));
temp.idEspecie = node.key;
tipoEspecie.add(temp);
});
return tipoEspecie;
Note: I'm not completely sure of the .forEach and the code in there. So if you get errors there, check what type you get back from .values and what node is, to get the correct key and values from it.

FLUTTER: returned value is NULL

I am working on an Exercises app.
I am using FIREBASE with a StreamBuilder to retrieve an "exercise id" that has been favorited in my app. This is working fine and I get the correct Id.
I then pass it to a simple method to retrieve the actual "exercise" so that I can display it. This method passes through a List of exercises, checking if the exercise id, is found, and then I want it to return the exercise.
However I am always getting null returned. I can't figure out why this is.
I'd really appreciate some help, as very stuck with it right now.
Here below is my code:
This is the method:
Exercise getExerciseByID(String exerciseId) {
_exercises.forEach((exercise) {
print('COMPARE1: ${exercise.id} AND $exerciseId');
if (exercise.id == exerciseId) {
print('COMPARE2: ${exercise.id} AND ${exerciseId}');
return exercise;
}
});
}
Note that the two print statements both print and show matching id's
And here is the Build with StreamBuilder etc..
return Scaffold(
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('favorites')
.doc('${_firebaseAuth.currentUser.uid}')
.collection('userFavorites')
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator(),
);
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
//TODO: GET EXERCISE FOR DOC
String _exerciseId = snapshot.data.docs[index].id;
_exercise = getExerciseByID(_exerciseId);
//TODO: DISPLAY CARD
print("Retrieved Exercise: $_exercise");
return Center(child: Text(""));
},
);
},
// padding: EdgeInsets.only(left: sideMargin),
// },
),
);
Note: the print statement: print("Retrieved Exercise: $_exercise"); is where I am always finding the NULL.
Many thanks for any assistance.
change this
Exercise getExerciseByID(String exerciseId) {
_exercises.forEach((exercise) {
print('COMPARE1: ${exercise.id} AND $exerciseId');
if (exercise.id == exerciseId) {
print('COMPARE2: ${exercise.id} AND ${exerciseId}');
return exercise;
}
});
}
to this
Exercise getExerciseByID(String exerciseId) {
Exercise returningExercise;
_exercises.forEach((exercise) {
print('COMPARE1: ${exercise.id} AND $exerciseId');
if (exercise.id == exerciseId) {
print('COMPARE2: ${exercise.id} AND ${exerciseId}');
returningExercise = exercise;
}
});
return returningExercise;
}
You are returning inside ForEach Statement. This doesn't return to your function.
put return statement outside ForEach statement.
Note: This solution is not best. But you now know whats going wrong.