StreamBuilder showing data only on hot reload in flutter - flutter

I am facing a problem with Streambuilder.
The workflow of my app is like there is a list of items when I press on add to favorites it adds the item to firestore. Then I am fetching data from firestore using stream and showing it on another screen using stream builder.
The problem is that the stream builder is empty when I hot reload the app the data shows perfectly.
this is the part where I am calling stream method
this is streambuilder
Thanks.

Try removing the extra variable resultStream and directly use the function DatabaseManager.getUserCurrency() in your StreamBuilder like this:
StreamBuilder(
stream: DatabaseManager.getUserCurrency(context),
builder: (context, snapshot){...}
)
if this does not fix the issue, check your getUserCurrency() function for errors. This is an example for getting a Stream from Firestore:
Stream<UserData> getUserData(String uid) {
try {
return userCollection
.doc(uid)
.snapshots()
.map((event) => event?.data() != null ? UserData.fromMap(event.data()) : null);
} catch (e) {
print(e);
return null;
}
}
Additionally you normally don’t have to await a Stream, which might cause the issue in your case, because you are calling the async function in your initState.

Related

Flutter, updating variable values not appearing in application

After defining a variable ("xyz" at screenshot), I fetched some data on my firestore database, then changed that variable value to data i fetched from firestore. When I print the changed variable with "print()" it appears at "Run" the value I fetched, which is what I want. But when I run the app, the text I assigned as changed variable appears on the screen with old value like I never changed it after defining. the code
When I print(xyz); it appears as the data from firestore, so there is no problem at database connection. I just want to update the value appears at screen too.
you could also listen to those snapshots without using StreamBuilder or any Flutter widget with the listen() method like this:
final fieldValue = "initial value";
FirebaseFirestore.instance
.doc(documentPath)
.snapshots()
.listen((snapshot) {
final data = snapshot.data() as Map<String, dynamic>;
fieldValue = data["field"];
print(fieldValue);
});
the fieldValue will be updated now every time an update happens to the document, and it will print it automatically in the console with the print()
as I see in the screenshot, you're getting your document's data with a Future, and the Future is useful when you need to call data just once :
FirebaseFirestore.instance.doc(documentPath).get(); // Future
FutureBuilder<DocumentSnapshot>(
future: FirebaseFirestore.instance.doc(documentPath).get(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasData) {
return Text("${snapshot.data!.data()}");
}
return Text("something else");
},
);
This will get the snapshot only once, even if you updated the document data, nothing will update on the screen until you make manually a new request to get another snapshot.
well the Firebase SDK for Flutter provide also Stream of snapshots for DocumentSnapshot and CollectionSnapshot, which listen directly to it, so whenever you have an update on your target, that stream will be notified and provide a new snapshot for that new updated data, and you can use it like this:
FirebaseFirestore.instance.doc(documentPath).snapshots(), // Stream
StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance.doc(documentPath).snapshots(), // Stream
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
final currentData = snapshot.data!.data() as Map<String, dynamic>;
final fieldValue = currentData["fieldString"];
return Text("$fieldValue");
}
return Text("something else");
},
);
change fieldString with your field name to get its value.
Now every update you do to the DocumentSnapshot, the StreamBuilder will get that new snapshot from the Stream and update the UI based on it.
Hope this helps!

Flutter: Weird listview/stream builder behavior

I have a home_page.dart file whose body has a StreamBuilder it recieves the stream from my getChats() function. In the StreamBuilder's builder function I sort the all the documents after storing them in a list named docsList based on the lastMessageTime field so that the document with the latest message is at the top (first).
As the ListView is using the docsList for building the cards it is expected that the document which has the most resent message should be displayed first. Which only happens when the list is build for the first time. After that if I send a new message to the chat which is not at the top this happens:
Initially:
When I send a message which the text 'test' to the chat "Heah Roger" this is how the list gets updated:
As it can be seen the Time on the right and the subtext changes for the first tile but the image and name didn't (same for second tile). Even though the documents are updated in the docsList and are sorted in the desired manner (I printed it to check it). Somehow the photo and the name alone are not being updated in the UI alone.
Note: The correct fields are updated in the firestore. Also if I restart the app after killing it. It shows the desired result:
getChats()
Stream<QuerySnapshot<Map<String, dynamic>>> getChats(User currentUser) {
return FirebaseFirestore.instance
.collection('chats')
.where('users', arrayContains: currentUser.id)
.snapshots();
}
home_page.dart
body: StreamBuilder(
stream: RepositoryProvider.of<FirestoreRepository>(context).getChats(BlocProvider.of<AuthenticationBloc>(context).state.user),
builder: (context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData && snapshot.data.docs.length > 0) {
List docsList = snapshot.data.docs;
docsList.sort((a, b) => b.data()['lastMessageTime'].compareTo(a.data()['lastMessageTime']));
return ListView.builder(
itemCount: docsList.length,
itemBuilder: (context, index) {
return SingleChatCard(chatRoom: ChatRoomModel.fromMap(docsList[index].data()));
},
);
} else {
return ...
}
},
),
Can anyone help me figure out the underlying problem that is causing this weird behavior?
Looks like an key issue to me,
When you're using your custom Widget to render in a listview, with some complicate data flow,
Flutter react to these changes one level at a time:
You can refer: https://www.youtube.com/watch?v=kn0EOS-ZiIc
In your example, you can do something like this:
return SingleChatCard(
key: ValueKey(index),
chatRoom: ChatRoomModel.fromMap(docsList[index].data()));
},

Function invoke in Flutter to get attributes from Firebase

I want to define and invoke a function in Flutter to get required values from Firebase.
In the below code I have defined getCourseDetails() function and invoking it in the container by passing a parameter to get the value.
The Course_Details collection has many documents with course_id's which has attribute with name (course_name, subtitle). I use these values to build a listview cards in next steps.
I am able to get the values from the function using async await, but for some reason the values keeps on updating and never stops. It kind of goes to loop and keeps on running. I added print statements to check and it keeps on running and printing.
Please let me know what wrong I am doing here or how to define function here to avoid the issue. Thanks
class _CourseProgressListState extends State<CourseProgressList> {
String course_name, subtitle;
getCourseDetails(course_id_pass) async {
DocumentSnapshot document = await Firestore.instance.collection('Course_Details').document(course_id_pass).get();
setState(() {
course_name = document.data['course_name'];
subtitle = document.data['subtitle'];
});
}
#override
Widget build(BuildContext context) {
return Container(
child: StreamBuilder(
stream: Firestore.instance.collection('Enrolling_Courses').where('student_id', isEqualTo: widget.id).snapshots(),
builder: (context, snapshot) {
if (snapshot.data == null) return Text('no data');
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (_, int index) {
var course_details = snapshot.data.documents[index];
getCourseDetails(course_details['course_id']);
Application Flow
On first build of CourseProgressList the Widget State _CourseProgressListState is loaded. This State in the build method uses a StreamBuilder to retrieve all documents from the firestore. As soon as the documents are received the StreamBuilder attempts to build the ListView using the ListViewBuilder.
In the ListViewBuilder you make the async call to getCourseDetails(course_details['course_id']); which when complete populates two attributes String course_name, subtitle;
The problem starts here
Problem
When you call
setState(() {
course_name = document.data['course_name'];
subtitle = document.data['subtitle'];
});
you trigger a Widget rebuild and so the process starts over again to rebuild the entire widget.
NB. refreshing state of a stateful widget will trigger a widget rebuild
NB. Firestore.instance.collection('Enrolling_Courses').where('student_id', isEqualTo: widget.id).snapshots() returns a stream of realtime changes implying that your List will also refresh each time there is a change to this collection
Recommendations
If you do not need to call the setState try not to call the setState.
You could let getCourseDetails(course_id_pass) return a Future/Stream with the values desired and use another FutureBuilder/StreamBuilder in your ListViewBuilder to return each ListViewItem. Your user may appreciate seeing some items instead of waiting for all the course details to be available
Abstract your request to firestore in a repository/provider or another function/class which will do the entire workload, i.e retrieving course ids then subsequently the course details and returning a Future<List<Course>> / Stream<List<Course>> for your main StreamBuilder in this widget (see reference snippet below as a guide and requires testing)
Reference Snippet
Your abstraction could look something like this but this decision is up to you. There are many software design patterns to consider or you could just start by getting it working.
//let's say we had a class Course
class Course {
String courseId;
String courseName;
String subTitle;
Course({this.courseId,this.courseName,this.subTitle})
}
Stream<List<Course>> getStudentCourses(int studentId){
return Firestore.instance
.collection('Enrolling_Courses')
.where('student_id', isEqualTo: studentId)
.snapshots()
//extract documents from snapshot
.map(snapshot => snapshot?.data ?? [])
//we will then request details for each document
.map(documents =>
/*because this is an asynchronous request for several
items which we are all interested in at the same time, we can wrap
this in
a Future.wait and retrieve the results of all as a list
*/
Future.wait(
documents.map(document =>
//making a request to firestore for each document
Firestore.instance
.collection('Course_Details')
.document(document['course_id'])
.get()
/* making a final transformation turning
each document into a Course item which we can easily pass to our
ListBuilder/Widgets
*/
.then(courseItem => Course(
courseId:document['course_id'],
courseName:
courseItem.data['course_name'],
subTitle:
courseItem.data['subtitle']
)
)
)
)
);
}
References/Resources
FutureBuilder - https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
StreamBuilder - https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
Future.wait - https://api.flutter.dev/flutter/dart-async/Future/wait.html

flutter: how to use 'await for' to wait for other BLoC event to finish

on screen init, I am loading my data via an externalData_bloc. On the same screen I am also rendering another widget controlled with internalData_bloc for user input which depends on the number of imported records (how many rows are needed). Of course, the rendering is faster than the data load, so I get null rows needed.
I found this nice question & answer, however, I do not get it to work. The solution says
Future loginProcess() async {
await for (var result in _userBloc.state) {
if (result.status == Status.finished) {
return;
}
}
}
I am within my repository. In here, I am also storing the external data in a variable. So within the repository class I have my function to get the number of records (properties are stored in the repository, and I want to return its length).
Future<int> getNoOfProperties(int problem) async {
LogicGraphsPStmntBloc bloc = LogicGraphsPStmntBloc();
Future propertiesLoad() async {
await for (var s in bloc) {
if (s == LogicGraphsPStmntLoadSuccess) {
return;
}
}
}
propertiesLoad();
return properties.length;
}
But no matter what I try to put in for await for (var result in XXXX) or if (s.YYY == ZZZ), it doesn't work. My understanding is, XXXX needs to be a stream which is why I used bloc = LogicGraphsPStmntBloc(). But I guess this creates another instance than the one used in my widgets. LogicGraphsPStmntBloc doesn't work either, nor do states. bloc at least doesn't throw an error already in the editor, but besides the instance issue, the if statement throws an error, where in cubit.dart StreamSubscription<State> listen(... is called with cancelOnError . Anyone having some enlightenment?
Bloc uses Stream and has a continuous flow of data. You might want to listen for changes in data instead of a finished task. Using a StreamBuilder is one way of doing this.
StreamBuilder<User>(
stream: bloc.userState, // Stream from bloc
builder: (context, AsyncSnapshot<State> snapshot) {
if (snapshot.hasData) {
// check if auth status is finished
}
}
)

Flutter Firestore, interacting with user?

Flutter: I'm using Streambuilder to listen to Firestore data which works great.
However, what I would like to do is to notify the user when certain conditions in Firestore changes, let him respond and write that back to Firestore.
Doable (in Flutter/Dart)?
Thanks in advance.
Best,
/j
StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
List<DocumentSnapshot> documents = snapshot.data.documents;
documents.forEach((doc) {
if (doc['Invited']) {
**// notify current user that he is invited,
// let him reply yes/no, write that value back to Firestore**
}
}
},
);
Is there a point why you dont want to use Firebase Messaging?
1) Firestore triggers a Notification
2) Your App recieve the notification
3) Your app handle the notification, it should be possible that the user dont see the notification in the status bar.
If i understand you correctly you want to update something as soon as the user receives an invitation. You can do this by using code like this:
Future<Null> updateUser{
#required String userId,
bool accepted,
}) {
assert(userId != null && userId.isNotEmpty);
// create map that contains all fields to update
Map<String, dynamic> dataToUpdate = {
"accepted": accepted,
};
// get the reference to the user you want to update
final userRef = references.users(userId);
// update the user
return userRef.updateData(dataToUpdate);
}
You can get the reference from your document by using doc.reference. The Future will complete if the update was successful or terminate with an error if something went wrong.