FutureBuilder's snapshot is always either null or has no data - firebase

I am fairly new to flutter and I hope this problem can be easily solved, but up until now I have no idea what's going wrong.
I have an app where I would like to return a list of users after the search button is pressed. I think that a FutureBuilder with a ListView.builder is the correct choice for this, but somehow I can't get my data to appear in the FutureBuilder even though my async function is definitely finding results.
My future:
Future<List<Map<dynamic, dynamic>>> results;
the button:
onPressed: () {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
setState(() {
results = null;
searched = true;
});
results = getGuestData(widget.place['placeName'], query);
}
},
Async function:
Future<List<Map>> getGuestData(String placeName, Map query) async {
List<Map> result = [];
CollectionReference guestsRef = Firestore.instance
.collection(XXX)
.document(XXX)
.collection(XXX);
if (query['date'] == null) {
guestsRef
.where('lastName', isEqualTo: query['lastName'])
.where('firstName', isEqualTo: query['firstName'])
.getDocuments()
.then((doc) {
if (doc != null) {
result = doc.documents.map((x) => x.data).toList(); \\debugger shows this is working
}
}).catchError((e) => print("error fetching data: $e"));
}
return result;
and finally the problem:
FutureBuilder<List<Map>>(
future: results,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
List guests = snapshot.data;
return ListView.builder(
shrinkWrap: true,
itemCount: guests.length,
itemBuilder: (context, index) {
return ListTile(
... //do stuff here
);
},
);
} else {
return CircularProgressIndicator();
}
},
)
snapshot.data will indeed turn out to be a List, but the size is always 0 despite the getGuestData query finding users.

Related

Flutter How to handle Stream and Future return error

Im trying to stream my firebase firestore fields. This is my code from my Database. It works in button and I can print what I want. But actually I want to show data with Widgets in my HomePage with StreamBuilder.
getYukListFromDB() async {
_firebaseFirestore
.collection('customer')
.get()
.then((QuerySnapshot querySnapshot) {
for (var docA in querySnapshot.docs) {
debugPrint("shipment altındaki docs idsi = " + docA.id);
_firebaseFirestore
.collection('customer')
.doc(docA.id)
.collection('myYuks')
.get()
.then((QuerySnapshot querySnapshot) {
for (var docB in querySnapshot.docs) {
debugPrint("myYuk altındaki docs idsi = " + docB.id);
_firebaseFirestore
.collection('customer')
.doc(docA.id)
.collection('myYuks')
.doc(docB.id)
.get()
.then((DocumentSnapshot documentSnapshot) {
Map<String, dynamic> mapData =
documentSnapshot.data()! as Map<String, dynamic>;
if (documentSnapshot.exists) {
debugPrint("icerik = ${mapData['icerik']}");
debugPrint('doc var');
} else {
debugPrint('doc yok');
}
});
}
});
}
});
}
When I try this in stream it gives me an error.
Stream<List<YukModel>> yeniYukStream(String uid) { // ***error here***
_firebaseFirestore
.collection('customer')
.get()
.then((QuerySnapshot querySnapshot) {
for (var doc1 in querySnapshot.docs) {
debugPrint("shipment altındaki docs idsi = " + doc1.id);
return _firebaseFirestore
.collection("customer")
.doc(doc1.id)
.collection('myYuks')
.get()
.then((QuerySnapshot querySnapshot) {
for (var doc2 in querySnapshot.docs) {
debugPrint("myYuk altındaki docs idsi = " + doc2.id);
_firebaseFirestore
.collection('customer')
.doc(doc2.id)
.collection('myYuks')
.orderBy('createTime', descending: true)
.snapshots()
.map((QuerySnapshot querySnapshot) {
List<YukModel> retVal = <YukModel>[];
for (var lastData in querySnapshot.docs) {
retVal.add(YukModel.fromDocumentSnapshot(lastData));
}
return retVal;
});
}
});
}
});
}
The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type.
Try adding either a return or a throw statement at the end.
And I know it should be Future. Let's try it.
Future<Stream<List<YukModel>>> yeniYukStream(String uid) async{
return _firebaseFirestore
.collection('customer')
.get()
.then((QuerySnapshot querySnapshot) { // ****error here****
for (var doc1 in querySnapshot.docs) {
debugPrint("shipment altındaki docs idsi = " + doc1.id);
return _firebaseFirestore
.collection("customer")
.doc(doc1.id)
.collection('myYuks')
.get()
.then((QuerySnapshot querySnapshot) { // ****error here****
for (var doc2 in querySnapshot.docs) {
debugPrint("myYuk altındaki docs idsi = " + doc2.id);
_firebaseFirestore
.collection('customer')
.doc(doc2.id)
.collection('myYuks')
.orderBy('createTime', descending: true)
.snapshots()
.map((QuerySnapshot querySnapshot) {
List<YukModel> retVal = <YukModel>[];
for (var lastData in querySnapshot.docs) {
retVal.add(YukModel.fromDocumentSnapshot(lastData));
}
return retVal;
});
}
});
}
});
}
**** error **** lines is this:
The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type.
Try adding either a return or a throw statement at the end.
And this is my YukModel.dart file;
class YukModel {
String? yukID;
String? yukBaslik;
String? icerik;
int? agirlik;
Timestamp? createTime;
String? aracTipi;
bool? onayDurumu;
YukModel(
{this.yukID,
this.yukBaslik,
this.icerik,
this.agirlik,
this.createTime,
this.aracTipi,
this.onayDurumu});
YukModel.fromDocumentSnapshot(DocumentSnapshot documentSnapshot) {
yukID = documentSnapshot.id;
yukBaslik = documentSnapshot.get('yukBaslik');
icerik = documentSnapshot.get('icerik');
agirlik = documentSnapshot.get('agirlik');
createTime = documentSnapshot.get('createTime');
aracTipi = documentSnapshot.get('aracTipi');
onayDurumu = documentSnapshot.get('onayDurumu');
}
}
What should I do? Also, I am using GetX package for state management.
MY SOLUTION FOR NOW
Here is my solution. It worked for me. Also, you can access your subcollections with this code.
In HomePage, I added a StreamBuilder:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("customer")
.doc(authController.doneUser!.uid) // you can use your uid
.collection("myYuks")
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.hasData && snapshot.data != null) {
if (snapshot.data!.docs.isNotEmpty) {
return ListView.builder(
itemBuilder: (context, int index) {
Map<String, dynamic> docData =
snapshot.data!.docs[index].data();
if (docData.isEmpty) {
return const Center(child: Text("Data empty"));
}
//these are my fields in subcollections.
// you can use like docData["yourfieldnameinsubcollection"];
String yukID = docData[FirestoreFields.yukID];
String yukBaslik = docData[FirestoreFields.yukBaslik];
String icerik = docData[FirestoreFields.icerik];
String agirlik = docData[FirestoreFields.agirlik];
return Card(
child: Container(
color: ColorConstants.birincilRenk,
height: 210,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
children: [
Text(yukID, style: const TextStyle(fontSize: 16)),
const Divider(thickness: 1),
Text(yukBaslik,
style: const TextStyle(fontSize: 20)),
const Divider(thickness: 1),
Text(icerik, style: const TextStyle(fontSize: 16)),
const Divider(thickness: 1),
Text(agirlik, style: const TextStyle(fontSize: 16)),
],
),
),
),
);
},
itemCount: snapshot.data!.docs.length,
);
} else {
return const Text("no data ");
}
} else {
return const Text("loading");
}
},
),
I didn't go through your entire code, because it was too much, but from the error message I can help you understand the issue.
Hers's a simplied version of what you might be trying to achive:
// This method returns the data from Firestore collection
Stream<T> getData(){
return collection.snapshots(); // This returns a Stream
// get() returns a Future
// snapshots() returns a Stream
}
// As the name says, it build from a STREAM
StreamBuilder(
future: getData(), // the source of stream
builder: (context,snapshot){
if(snapshot.hasData){ // checking if the stream's snapshot has data
return Text(snapshot.data!); // If there is data, we display widgets accordingly
}else{
return const CircularProgressIndicator(); // If there isn't data yet, we display a CircularProgressIndicator
}
}
)
Now for your issue, as the error message says:
The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type. Try adding either a return or a throw statement at the end.
Here's the example to explain that:
StreamBuilder(
future: getData(), // the source of stream
builder: (context,snapshot){
if(snapshot.hasData){ // checking if the stream's snapshot has data
return Text(snapshot.data!); // If there is data, we display widgets accordingly
}
}
)
If you noticed, I don't use the else statement here, so what that means is that:
If snapshot has data then display a widget, but what if the snapshot doesn't hava data yet, then what will be displayed. I will receive the same error as you. Soo to solve that, I use an else statement. This way my
my body DOESN'T return null
The Streambuilder can be listened to by subscribers. The MyClassBloc contains a get method called MyClassListStream. The MyClassBloc contains a datastream of list type. MyClassBlock can be assigned data from an web api provider to the listMyClass variable. in the _loadMyClassItems method a call to initializeStream is called notifying all subscribers there is data on the stream. The _buildList method has a streambuilder which stream points to the MyClassBloc stream controller get MyClassListStream assessing _MyClassListSubject.stream and the initialData references widget.blocMyClass.listMyClass. When data arrives on the stream, the Streambuilder maps data from the stream to create a list of CardWidgets. The snapshot.data is of MyClass Type. Therefore _buildList returns a list of cardwidgets created from the stream. Everything starts when initState() calls the _loadMyClassItems and the web api returns a list of MyClass objects after the promise is completed and assigns the data to the MyClassBloc variable and assigns data and invokes the initializeTheStream. The Streambuilder is notified that data is on the stream from the MyClassBloc _MyClassListSubject.add(listMyClass) event. The Streambuilder then returns a list of CardWidgets.
class MyClassBloc {
List<MyClass> listMyClass;
Stream<List<MyClass>> get MyClassListStream =>
_MyClassListSubject.stream;
final _MyClassListSubject =
BehaviorSubject<List<MyClass>>();
initializeTheStream() {
if (listMyClass != null) {
_MyClassListSubject.add(listMyClass);
}
}
dispose() {
_MyClassListSubject.close();
}
}
class MyClass
{
int field1;
String field2;
MyClass(
this.field1,
this.field2,
);
Map<String,dynamic> toJson(){
var map={
'field1': field1,
'field2': field2,
};
return map;
}
factory MyClass.fromJson(Map<String,dynamic> json){
if(json==null){throw FormatException("null json");}
return MyClass(
json['field1'] as int,
json['field2'] as String,
);
}
}
_loadMyClassItems(BuildContext context) {
try {
Provider.of<ApiWidget>(context, listen: false)
.getMyClass()
.then((value) {
setState(() {
loadSummary = true;
if (value != null) {
widget.blocSummary.listMyClass = value;
widget.blocSummary.initializeTheStream();
} else {
widget.blocSummary.listMyClass =
[];
widget.blocSummary.initializeTheStream();
}
});
}).catchError((error) {
DialogCaller.showErrorDialog(context, error);
});
} catch (e) {
DialogCaller.showErrorDialog(context, e.toString());
}
}
Widget _buildList(BuildContext context) {
List<Widget> cardWidgets;
return StreamBuilder<List<MyClass>>(
stream: widget.blocMyClass.MyClassListStream,
initialData: widget.blocMyClass.listMyClass,
builder: (context, snapshot) {
//debugPrint(snapshot.connectionState.toString());
if (!snapshot.hasData) {
return PleaseWaitWidget();
} else if (snapshot.hasError) {
DialogCaller.showErrorDialog(
context, "future builder in main has an error")
.then((value) {});
} else if (snapshot.hasData) {
//debugPrint("reached processing");
cardWidgets =
snapshot.data.map((MyClass MyClass) {
return CardWidget(MyClass);
}).toList();
return CardWidgets.length == 0
? Container(
padding: EdgeInsets.all(20),
child: Text("No Records found", style: NoRecordFoundStyle))
: ListView(children: CardWidgets);
}
return (Container(child: Text("Failed to build list")));
});
}
myWidget.dart
void initState(){
super.initState();
_loadMyClassItems();
}
Widget build(BuildContext context) {
if (loading == false) {
return PleaseWaitWidget();
} else {
return new Scaffold(
key: _scaffoldKey,
....
body: Column(children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(child: _buildList(context)),
]

FutureBuilder can't "see" data / snapshot.hasData

I have a Future function to get data from Firebase, that is not empty queries correctly:
Future getProducts(vendorId) async {
await vendorsInfoColl.doc(vendorId)
.collection("products")
.get()
.then((QuerySnapshot querySnapShot) async {
if (querySnapShot.docs.isNotEmpty){
print('not empty');
print(querySnapShot.docs);
return querySnapShot.docs;
} else {
print('snapshot empty');
return null;
}
});
}
I just have trouble getting a FutureBuilder see it. Keeps saying there is empty data.
Widget build(BuildContext context) {
return AlertDialog(
// pull title from db
// title: Future Text(vendorTitle.toString()),
title: Text(vendorTitle.toString()),
content: FutureBuilder(
future: VendorDatabase().getProducts(widget.vendorId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
print(snapshot.connectionState);
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return const Text('Error');
} else if (snapshot.hasData) {
var blabla = snapshot.data;
print('there is snapshot data: $blabla');
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return ListTile(
title: Text(blabla['products']) //<-- i know this is wrong but can't fixt it till future builder actually sees some data.
);
}
);
} else {
return const Text('Empty data');
}
} else {
return Text('State: ${snapshot.connectionState}');
}
}
)
);
}
It would be great if can also get some tips on how to put it in a list :)
You're not returning anything inside getProducts:
Future getProducts(vendorId) async {
return await vendorsInfoColl.doc(vendorId)
.collection("products")
.get()
.then((QuerySnapshot querySnapShot) async {
if (querySnapShot.docs.isNotEmpty){
return querySnapShot.docs;
} else {
return null;
}
});
}
To avoid this in the future, declare a type in your Future:
// vvvvvvvvvvvvvvvv
Future<List<Document>> getProducts(vendorId) async {

Flutter - Class 'DocumentSnapshot' has no instance getter 'docs'

I have 2 streams in my codes the first one is to get the userid from friend list and the second stream is to use the list of ids to search for the userid's document in firebase.
Stream friendIDStream;
Stream friendNameStream;
Widget friendList() {
return StreamBuilder(
stream: friendNameStream,
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
return FriendTile(
snapshot.data.docs[index].data()["username"]);
},
)
: Container();
},
);
}
#override
void initState() {
getUserFriend();
getNameByID();
super.initState();
}
getUserFriend() async {
Constant.currentId =
await HelperFunctions.getUserIdSharedPreference(Constant.currentId);
setState(() {
firebaseMethods.getFriend(Constant.currentId).then((value) {
setState(() {
friendIDStream = value;
});
});
});
}
getNameByID() {
setState(() {
firebaseMethods.getFriendName(friendIDStream).then((value) {
setState(() {
friendNameStream = value;
});
});
});
}
This is the firestore code.
Future getFriend(String ownerid) async {
return await FirebaseFirestore.instance
.collection("users")
.doc(ownerid)
.collection("friends")
.snapshots();
}
Future getFriendName(friendid) async {
return await FirebaseFirestore.instance
.collection("users")
.doc(friendid)
.snapshots();
}
I doesn't know why is this happening since I can display the list of ids. I had tried changing docs to doc but is also produce the same error.
Edit:
Added photos of my database structure.
the reason is your function getFriendName is returning a documentsnapshot not a querysnapshot. SO replace your old code with this:-
Widget friendList() {
return StreamBuilder(
stream: friendNameStream,
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
itemCount: 1,
itemBuilder: (context, index) {
return FriendTile(
//snapshot.data.docs[index].data()["username"] old one
snapshot.data["username"]); //new one
},
)
: Container();
},
);
}

flutter: check if the document exist

I want to check whether the document exist or not without creating the document if it does not exits
Checked() {
Future<DocumentSnapshot> check = linkref.
document(user.UID).
collection("Requests").
document(uuid).get();
return FutureBuilder(
future: check,
builder: (context, ccheck) {
if (check != null ) {
return Text("Available");
}
return Text("not available);
});
}
i tried this code but even if the document does not exists it says that it exists
You should use; if (ccheck.data.exists) instead of if (check != null ). Here is the code;
Checked() {
Future<DocumentSnapshot> check =
linkref.document(user.UID).collection("Requests").document(uuid).get();
return FutureBuilder<DocumentSnapshot>(
future: check,
builder: (context, ccheck) {
if (ccheck.data.exists) {
return Text("Available");
}
return Text("not available");
});
}
You can use the .where( field, isEqualTo: query). This might be useful to you.
final userRef = FirebaseFirestore.instance.collection('users');
checkExists(String query) async {
QuerySnapshot checker = await userRef
.where('uid', isEqualTo: query)
.get();
chkr.docs.forEach((doc) {
if (doc.exists) {
print('Exists');
print(doc.get('uid'));
}else {
print('false');
}
});
}
Then, if you are using a button, you can use onPressed: () => check(yourQuery).

StreamBuilder Firestore Pagination

I m new to flutter, and I'm trying to paginate a chat when scroll reach top with streambuilder. The problem is: when i make the query in scrollListener streambuilder priorize his query above the scrollListener and returns de old response. Is there any way to do this? What are my options here? Thanks!
Class ChatScreenState
In initState I create the scroll listener.
#override
void initState() {
listScrollController = ScrollController();
listScrollController.addListener(_scrollListener);
super.initState();
}
Here i create the StreamBuilder with the query limited to 20 last messages. Using the _messagesSnapshots as global List.
#override
Widget build(BuildContext context) {
return Scaffold(
key: key,
appBar: AppBar(title: Text("Chat")),
body: Container(
child: Column(
children: <Widget>[
Flexible(
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('messages')
.where('room_id', isEqualTo: _roomID)
.orderBy('timestamp', descending: true)
.limit(20)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
_messagesSnapshots = snapshot.data.documents;
return _buildList(context, _messagesSnapshots);
},
)),
Divider(height: 1.0),
Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTextComposer(),
),
],
),
));
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
_messagesSnapshots = snapshot;
return ListView.builder(
controller: listScrollController,
itemCount: _messagesSnapshots.length,
reverse: true,
itemBuilder: (context, index) {
return _buildListItem(context, _messagesSnapshots[index]);
},
);
}
And in the _scollListener method i query the next 20 messages and add the result to the Global list.
_scrollListener() {
// If reach top
if (listScrollController.offset >=
listScrollController.position.maxScrollExtent &&
!listScrollController.position.outOfRange) {
// Then search last message
final message = Message.fromSnapshot(
_messagesSnapshots[_messagesSnapshots.length - 1]);
// And get the next 20 messages from database
Firestore.instance
.collection('messages')
.where('room_id', isEqualTo: _roomID)
.where('timestamp', isLessThan: message.timestamp)
.orderBy('timestamp', descending: true)
.limit(20)
.getDocuments()
.then((snapshot) {
// To save in the global list
setState(() {
_messagesSnapshots.addAll(snapshot.documents);
});
});
// debug snackbar
key.currentState.showSnackBar(new SnackBar(
content: new Text("Top Reached"),
));
}
}
I'm gonna post my code i hope someone post a better solution, probably is not the best but it works.
In my app the actual solution is change the state of the list when reach the top, stop stream and show old messages.
All code (State)
class _MessageListState extends State<MessageList> {
List<DocumentSnapshot> _messagesSnapshots;
bool _isLoading = false;
final TextEditingController _textController = TextEditingController();
ScrollController listScrollController;
Message lastMessage;
Room room;
#override
void initState() {
listScrollController = ScrollController();
listScrollController.addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
room = widget.room;
return Flexible(
child: StreamBuilder<QuerySnapshot>(
stream: _isLoading
? null
: Firestore.instance
.collection('rooms')
.document(room.id)
.collection('messages')
.orderBy('timestamp', descending: true)
.limit(20)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
_messagesSnapshots = snapshot.data.documents;
return _buildList(context, _messagesSnapshots);
},
),
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
_messagesSnapshots = snapshot;
if (snapshot.isNotEmpty) lastMessage = Message.fromSnapshot(snapshot[0]);
return ListView.builder(
padding: EdgeInsets.all(10),
controller: listScrollController,
itemCount: _messagesSnapshots.length,
reverse: true,
itemBuilder: (context, index) {
return _buildListItem(context, _messagesSnapshots[index]);
},
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final message = Message.fromSnapshot(data);
Widget chatMessage = message.sender != widget.me.id
? Bubble(
message: message,
isMe: false,
)
: Bubble(
message: message,
isMe: true,
);
return Column(
children: <Widget>[chatMessage],
);
}
loadToTrue() {
_isLoading = true;
Firestore.instance
.collection('messages')
.reference()
.where('room_id', isEqualTo: widget.room.id)
.orderBy('timestamp', descending: true)
.limit(1)
.snapshots()
.listen((onData) {
print("Something change");
if (onData.documents[0] != null) {
Message result = Message.fromSnapshot(onData.documents[0]);
// Here i check if last array message is the last of the FireStore DB
int equal = lastMessage?.compareTo(result) ?? 1;
if (equal != 0) {
setState(() {
_isLoading = false;
});
}
}
});
}
_scrollListener() {
// if _scroll reach top
if (listScrollController.offset >=
listScrollController.position.maxScrollExtent &&
!listScrollController.position.outOfRange) {
final message = Message.fromSnapshot(
_messagesSnapshots[_messagesSnapshots.length - 1]);
// Query old messages
Firestore.instance
.collection('rooms')
.document(widget.room.id)
.collection('messages')
.where('timestamp', isLessThan: message.timestamp)
.orderBy('timestamp', descending: true)
.limit(20)
.getDocuments()
.then((snapshot) {
setState(() {
loadToTrue();
// And add to the list
_messagesSnapshots.addAll(snapshot.documents);
});
});
// For debug purposes
// key.currentState.showSnackBar(new SnackBar(
// content: new Text("Top reached"),
// ));
}
}
}
The most important methods are:
_scrollListener
When reach the top i query old messages and in setState i set isLoading var to true and set with the old messages the array i m gonna show.
_scrollListener() {
// if _scroll reach top
if (listScrollController.offset >=
listScrollController.position.maxScrollExtent &&
!listScrollController.position.outOfRange) {
final message = Message.fromSnapshot(
_messagesSnapshots[_messagesSnapshots.length - 1]);
// Query old messages
Firestore.instance
.collection('rooms')
.document(widget.room.id)
.collection('messages')
.where('timestamp', isLessThan: message.timestamp)
.orderBy('timestamp', descending: true)
.limit(20)
.getDocuments()
.then((snapshot) {
setState(() {
loadToTrue();
// And add to the list
_messagesSnapshots.addAll(snapshot.documents);
});
});
// For debug purposes
// key.currentState.showSnackBar(new SnackBar(
// content: new Text("Top reached"),
// ));
}
}
And loadToTrue that listen while we are looking for old messages. If there is a new message we re activate the stream.
loadToTrue
loadToTrue() {
_isLoading = true;
Firestore.instance
.collection('rooms')
.document(widget.room.id)
.collection('messages')
.orderBy('timestamp', descending: true)
.limit(1)
.snapshots()
.listen((onData) {
print("Something change");
if (onData.documents[0] != null) {
Message result = Message.fromSnapshot(onData.documents[0]);
// Here i check if last array message is the last of the FireStore DB
int equal = lastMessage?.compareTo(result) ?? 1;
if (equal != 0) {
setState(() {
_isLoading = false;
});
}
}
});
}
I hope this helps anyone who have the same problem (#Purus) and wait until someone give us a better solution!
First of all, I doubt such an API is the right backend for a chat app with live data - paginated APIs are better suited for static content.
For example, what exactly does "page 2" refer to if 30 messages were added after "page 1" loaded?
Also, note that Firebase charges for Firestore requests on a per-document basis, so every message which is requested twice hurts your quota and your wallet.
As you see, a paginated API with a fixed page length is probably not the right fit. That why I strongly advise you to rather request messages that were sent in a certain time interval.
The Firestore request could contain some code like this:
.where("time", ">", lastCheck).where("time", "<=", DateTime.now())
Either way, here's my answer to a similar question about paginated APIs in Flutter, which contains code for an actual implementation that loads new content as a ListView scrolls.
I have a way to archive it. Sorry for my bad english
bool loadMoreMessage = false;
int lastMessageIndex = 25 /// assumed each time scroll to the top of ListView load more 25 documents When I scroll to the top of the ListView =>setState loadMoreMessage = true;
This is my code:
StreamBuilder<List<Message>>(
stream:
_loadMoreMessage ? _streamMessage(lastMessageIndex): _streamMessage(25),
builder: (context, AsyncSnapshot<List<Message>> snapshot) {
if (!snapshot.hasData) {
return Container();
} else {
listMessage = snapshot.data;
return NotificationListener(
onNotification: (notification) {
if (notification is ScrollEndNotification) {
if (notification.metrics.pixels > 0) {
setState(() {
/// Logic here!
lastMessageIndex = lastMessageIndex + 25;
_loadMoreMessage = true;
});
}
}
},
child: ListView.builder(
controller: _scrollController,
reverse: true,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ChatContent(listMessage[index]);
},
),
);
}
},
),

Resources