How to observe Firebase changes in Flutter using StreamBuilder - flutter

I am trying to observe Firebase collection changes. In the first run, Listview is not building. After changing data on Firebase, then listview is rendered properly but previous snapshot is used because I see my old data on the screen.
Any idea to find the error?
#override
Widget build(BuildContext context) {
return BlocListener<VoteCubit, VoteState>(
listener: (context, state) {
if (state.status == FormStatus.success) {
print("Success");
} else if (state.status == FormStatus.inprogress) {
print("In progress");
} else if (state.status == FormStatus.fail) {
print("fail");
} else {
print("Empty Area");
}
},
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('liveSurvey').snapshots(),
builder: (context, snapshot) {
final voteCubit = context.read<VoteCubit>();
final voteState = voteCubit.state;
if (snapshot.hasData) {
voteCubit.getAllRooms(snapshot);
print("Here");
return ListView.builder(
itemCount: voteState.rooms.length,
itemBuilder: (context, i) {
print("Inside Listview");
. . .

Use stream builder like this for real time data.
child: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("travellers/India/details")
.where(
'firestore_id',
isEqualTo: widget.get_firestore_id,
)
.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
print('play lodader')
}else if (snapshot.hasData) {
print('your_ui_here')
}

Related

Listview.builder inside StreamBuilder is rebuilding/flickering

So I used Streambuilder to fetch CartItems from Firestore and it starting to flicker and it always goes to the top. Also can't scroll down, it always goes to the top, I used streambuilder in different parts of the app and it works perfectly but here it causing the problem...
Stream<QuerySnapshot<Map<String, dynamic>>> getCartStream() {
CollectionReference<Map<String, dynamic>> ref = FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.collection("cart");
Stream<QuerySnapshot<Map<String, dynamic>>> stream = ref.snapshots();
return stream;
}
StreamBuilder(
stream: getCarStream(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Container();
} else {
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
ProductModel productModel = ProductModel.fromMap(snapshot.data!.docs[index].data());
return CartItem(product: productModel);
},
);
}
},
)),
Rest of the code
Gif of the Problem
just remove the initState(), will work fine. if not then kindly share CARTITEM(..) class.

Modify Future Builder inside Stream Builder to avoid widget flickering

I am using a FutureBuilder inside a StreamBuilder that updates the UI every time a document is added to the activity collection, to get some aditional data from Firestore. The problem is that the FutureBuilder returns a SizedBox widget while the ConnectionState is waiting causing the all the cards to dissapear for a second. I would like to avoid this flickering since it causes a bad ui experience for users.
Is there a way to query the required user data in the activity stream so it all returns at once that way I can remove the FutureBuilder?
If not ... what would be a solution for this?
activityStream() {
return FirebaseFirestore.instance
.collection('activity')
.orderBy('timestamp', descending: true)
.limit(55)
.snapshots();
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
StreamBuilder<QuerySnapshot>(
stream: activityStream(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const Center(child: CircularProgressIndicator());
default:
final activityContent = snapshot.data?.docs
.map((data) => ActivityModel.fromFirestore(data))
.toList();
return Scrollbar(
controller: widget.scrollController,
child: ListView.builder(
shrinkWrap: true,
controller: widget.scrollController,
itemCount: activityContent!.length,
itemBuilder: (context, i) {
return FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.where('uid', whereIn: activityContent[i].players)
.get(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
final users = snapshot.data!.docs.map((e) {
return UserModel.fromFirestore(e);
}).toList();
return MyWidget(
users: users,
);
},
);
},
),
);
}
},
);

how can i compound firestore datas

I want to compound firestore datas. Which widget is useful for multiple data?
For instance, i have user datas and user post's datas. I want to show them in the same Card Widget, but their snapshots are different. How can i compound them ?
I write just like this, but it shows just 1 snapshot, i want to both futurebuilder and streambuilder snapshots:
note: i can't use user datas, in below it has final dataUser = snapshotUser.data;
but i can't use it, it gives an error
class HomePageBodyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) => StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collectionGroup('Posts').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
return ListView(
children: snapshot.data.docs.map((DocumentSnapshot docPost) {
return FutureBuilder(
future: FirebaseFirestore.instance
.collection('Users')
.doc(docPost.reference.parent.parent.id)
.get(),
builder: (context, snapshotUser) {
if (!snapshotUser.hasData) {
return Center(child: CircularProgressIndicator());
}
return homePageCard(context, dataPost: docPost, dataUser: snapshotUser.data);
},
);
}).toList());
});
}
i solved it, like this:
class HomePageBodyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) => StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collectionGroup('Posts').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
final dataPost = snapshot.data.docs[index];
return FutureBuilder(
future: FirebaseFirestore.instance
.collection('Users')
.doc(dataPost.reference.parent.parent.id)
.get(),
builder: (context, snapshotUser) {
final dataUser = snapshotUser.data;
if (!snapshotUser.hasData) {
return Center(child: CircularProgressIndicator());
}
return homePageCard(context,
dataPost: dataPost, dataUser: dataUser);
},
);
});
});
}

Wait a function to be done in ListView

I need to wait my function to be done in a ListView because of a Firestore request. I tried using Future.wait() but it does not work.
FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection('Statements')
.where('userID',isEqualTo: context.watch<User>().uid)
.get(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if (snapshot.hasError) {return Text("Erreur");}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");}
return Expanded(
child:ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot document = snapshot.data.docs[index];
Future.wait([getStatementData(document)]);
return StatementCard(statement:selectedStatement,
accommodation : relatedAccommodation,
owner : relatedOwner);
},
)
Here is the function called :
Future getStatementData(document) async {
selectedStatement = Statement.fromFirebase(document);
document.data()["accommodation"].get().then((value) {
relatedAccommodation = Accommodation.fromFirebase(value);});
await FirebaseFirestore.instance
.collection('Owners')
.doc(document["ownerList"][0])
.get().then((value) {
print(value.data());
relatedOwner = Owner.fromFirebase(value);
});
}
Should I use another future builder ?
You should do it like this by returning another Future Builder inside :
ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot document = snapshot.data.docs[index];
return FutureBuilder(
future: getStatementData(document) ,
builder: (context, snapshot) {
return snapshot.connectionState == ConnectionState.done
? StatementCard(statement:selectedStatement,
accommodation : relatedAccommodation,
owner : relatedOwner);
: Container();
);
},
)
I think you just missed a simple thing, FutureBuilder also has a connectionstate.done state which you can access. This waits until your future function is Done.
return FutureBuilder(
future: yourFunction,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Center(child: Text('No status',);
break;
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
break;
case ConnectionState.done:
return Text('Im done!') // Your listviewbuilder comes here
break;
default:
}
},
),
doc: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
example FutureBuilder with ListViewBuilder: https://medium.com/nonstopio/flutter-future-builder-with-list-view-builder-d7212314e8c9
[...]connectionState.done = [future] is not null, and has completed. If the future completed successfully, the [AsyncSnapshot.data] will be set to the value to which the future completed. If it completed with an error, [AsyncSnapshot.hasError] will be true and [AsyncSnapshot.error] will be set to the error object.[...]

FutureBuilder doesn't work properly with Firestore

can't create a query using FutureBuilder.
Firestore is at the latest version.
This is the case for both Android and iOS devices, any ideas?
return FutureBuilder<QuerySnapshot>(
future: Firestore.instance
.collection('productlist')
.where('productid', isEqualTo: pid)
.limit(
1), // a previously-obtained Future<String> or null
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
productname = snapshot.data.documents[0]['productname'];
print("productname:" + productname);
}
else{
productname = "0";
}
},
);
I think the issue here is that you are not returning a widget.
Try the code below
FutureBuilder(
future: Firestore.instance
.collection('productlist')
.where('productid', isEqualTo: pid)
.limit(1),
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return Text(snapshot.data.documents[0]['productname']);
} else {
return Text("0");
}
},
),
u can use stream builder instead it works pretty well :
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('productlist').where('productid', isEqualTo: pid).limit(1),
builder: (context, snapshot) {
if (snapshot.data != null) {
// Here u will get list of document snapshots
final List<DocumentSnapshot> documents = snapshot.data.documents;
// now u can access each document by simply specifying its number
// u can also use list view to display every one of them
return ListView.builder(
itemCount: documents.length,
itemBuilder: (context, int index) => Text(documents[index]['productname']),
);
} else {
// Show loading indicator here
}
},
);