Modify Future Builder inside Stream Builder to avoid widget flickering - flutter

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,
);
},
);
},
),
);
}
},
);

Related

Fetching data from Firebase into listview in flutter

I'm trying to fetch data from firebase and I want that data to be printed in my listview.builder.
Here is my code:-
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("passwordProfile")
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
if (snapshot.data.documents.lenght == 0) {
return Text("no data");
}
return ListView.builder(
itemCount: snapshot.data.documents.lenght,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: snapshot.data.documents[index].data("url")),
title: snapshot.data.documents[index].data["url"],
);
});
}),
And below is a picture:-
From the picture you can observe that documents is not being recognized so help there.
Please specify SteamBuilder return type i-e Querysnapshot here's the example of your code
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("passwordProfile")
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
final userSnapshot = snapshot.data?.docs;
if (userSnapshot!.isEmpty) {
return const Text("no data");
}
return ListView.builder(
itemCount: userSnapshot.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: userSnapshot[index]["url"],
),
title: userSnapshot[index]["url"],
);
});
});
Once you check null, you can add ! like snapshot.data!
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
final data = snapshot.data as Map<String, dynamic>?;
if (data == null) {
return Text("no data");
}
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
// leading:
// CircleAvatar(child: data[index].data("url")),
title: Text("${data[index].data["url"]}"),
);
});
}),

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.[...]

How do I get only the current users posts to show?

I'm trying to build an app in flutter and I have come up against this problem which I can't seem to find any existing answers. How do I get only the current users posts to show? My posts collection has a user id field which I want to compare with the current user and display only the post where the userId and currentUser are the same.
return FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (ctx, futureSnapshot) {
if (futureSnapshot.connectionState == ConnectionState.waiting) {
Center(
child: CircularProgressIndicator(),
);
}
return StreamBuilder(
stream: Firestore.instance.collection('posts').snapshots(),
builder: (context, streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
Center(
child: CircularProgressIndicator(),
);
}
final documents = streamSnapshot.data.documents;
return ListView.builder(
itemCount: documents.length,
itemBuilder: (ctx, index) => PostItem(
documents[index]['title'],
documents[index]['imageUrl'],
documents[index]['location']['address'],
));
});
});
here is my post collection structure
You're currently getting all posts with:
Firestore.instance.collection('posts').snapshots()
If you only want posts for the current user, that'd be something like:
var uid = (await FirebaseAuth.instance.currentUser()).uid;
Firestore.instance.collection('posts').where('uid', isEqualTo: uid).snapshots()
The first line determines the UID of the current user, and then the second line uses that to request only documents whose uid field matches the value.
I just realised I hadn't but my future into a variable for the streambuilder where clause !! I was trying to use the original fireauth currentuser!
return FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (ctx, futureSnapshot) {
if (futureSnapshot.connectionState == ConnectionState.waiting) {
Center(
child: CircularProgressIndicator(),
);
}
**final String currentUser** = futureSnapshot.data.uid;
return StreamBuilder(
stream: Firestore.instance
.collection('posts')
.where('userId', isEqualTo: **currentUser**)
.snapshots(),
builder: (context, streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
Center(
child: CircularProgressIndicator(),
);
}
final documents = streamSnapshot.data.documents;
return ListView.builder(
itemCount: documents.length,
itemBuilder: (ctx, index) => PostItem(
documents[index]['userId'],
documents[index]['title'],
documents[index]['imageUrl'],
documents[index]['location']['address'],
));
});
});
```

Update ListView.builder itemCount when firestore document is added

I have a flutter app where a list is generated with ListView.Builder, and where the itemCount is the number of documents in a firestore collection. This works fine until a new document is added. When that happens I get the error (17 and 18 are just examples).
Invalid value: Not in range 0..17, inclusive: 18
I assume I would need to update the state when a new document is created, but I have no idea how i can call setState when that happens
Here is the relevant part of the code:
child: StreamBuilder(
stream: Firestore.instance.collection('contact').orderBy(sortby, descending: decending).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_personer(context, snapshot.data.documents[index], index),
);
},
),
use setState?
StreamBuilder(builder: (context, snapshot) {
return snapshot.hasData == null ? Container() : _getListView(snapshot);
} , )
_getListView(snapshot) {
setState(() {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_personer(context, snapshot.data.documents[index], index),
);
});
}
StreamBuilder use QuerySnapshot so list data can change
example code :
StreamBuilder<QuerySnapshot>(
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading...');
default:
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return ;
}).toList(),
);
}
},
)