How can I loop over an array in firebase? - firebase

Im trying to get data from firebase. But im a bit struggling with that heres how it looks now
getusers() async {
var firestore = FirebaseFirestore.instance;
List listOfIds = [];
QuerySnapshot qn= await firestore
.collection('videos')
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
setState(() {
});
});
});
if (!mounted) return;
_allResults =qn.docs;
What I want is get the hashtasg array field and then add it to the qn.doc data in _allresults . But how can I do that ?
Heres my firebase so you can see how it looks
And last step I wanna loop over the howle hashtag array
This is my widget
class Openalldocs extends StatefulWidget {
final TextEditingController searchinginput;
static const route = '/openalldocs';
const Openalldocs({Key key, this.searchinginput}) : super(key: key);
#override
_OpenalldocsState createState() => _OpenalldocsState();
}
class _OpenalldocsState extends State<Openalldocs> {
List _allResults = [];
List _resultsList = [];
Future resultsLoaded;
bool nosuerfound = false;
String searchresult;
#override
void initState() {
super.initState();
widget.searchinginput.addListener(_onsearchChanged);
setState(() {
nosuerfound = true;
});
}
#override
void dispose() {
widget.searchinginput.removeListener(_onsearchChanged());
super.dispose();
}
#override
void didChangeDependencies() {
widget.searchinginput.text;
resultsLoaded = getusers();
super.didChangeDependencies();
}
_onsearchChanged() {
setState(() {
nosuerfound = false;
});
searchResults();
}
searchResults() {
var showResults = [];
if (widget.searchinginput.text != "") {
for (var tripsnapshot in _allResults) {
var title = DatbaseService.instance
.videosfromsnapshot(tripsnapshot)
.hashtag1
.toLowerCase();
var title2 = DatbaseService.instance
.videosfromsnapshot(tripsnapshot)
.hashtag2
.toLowerCase();
var title3 = DatbaseService.instance
.videosfromsnapshot(tripsnapshot)
.hashtag3
.toLowerCase();
if (title.contains(widget.searchinginput.text.toLowerCase()) ||
title2.contains(widget.searchinginput.text.toLowerCase()) ||
title3.contains(widget.searchinginput.text.toLowerCase())) {
setState(() {
nosuerfound = true;
});
showResults.add(tripsnapshot);
}
}
} else {
setState(() {
nosuerfound = true;
});
showResults = List.from(_allResults);
}
setState(() {
_resultsList = showResults;
});
}
getusers() async {
var firestore = FirebaseFirestore.instance;
List listOfIds = [];
QuerySnapshot qn= await firestore
.collection('videos')
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
setState(() {
_allResults.add(doc.data()["hashtag1"]);
});
});
});
if (!mounted) return;
searchResults();
return "Complete";
}
#override
Widget build(BuildContext context) {
final user = Provider.of<Userforid>(context);
if (nosuerfound == true) {
return ListView.builder(
itemCount: _resultsList.length,
itemBuilder: (BuildContext context, int index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// the AMOUNT is how many hashtags you want to show
for (var i = 0; i < _resultsList.length; i += 1) ...[
// the SizedBox will only exist between the elements in the list
// as before
if (i != 0) SizedBox(height: 6),
// create a builder to allow declaring a variable
Builder(
builder: (context) {
// declare the hashtag variable
final hashtag = 'hashtag${i + 1}';
return InkWell(
onTap: () {
// do something with the hashtag stored in the variable
// this will make it relative to the element in the list
},
child: Column(
children: <Widget>[
// why is there a Column inside another with only one child?
// I would recommend to remove it
Column(
children: [
HighlightedMatchesText(
searchString: widget.searchinginput.text,
// notice how I am using the hashtag variable here
// instead of a constant? ('hashtag1'), by the way
// the for loop will make the hashtag start at 0
// you can change it by increment in the declaration
// `final hashtag = 'hashtag${i+1}'`, if you want
// the existing behavior
content: _resultsList[index][hashtag],
),
],
),
// what is this? if it is to add more space between the items
// in the list, I recommend removing it from here, and add it
// to the first `SizedBox` in the for loop
// in case you do that, the Column that this widget belong
// would also only now contain one widget, so, there is no
// need to have it
SizedBox(height: 3),
],

You are using the Firestore methods correctly, the querySnapshot.docs is an array of all documents in that collection that you are looping through with forEach - You only require further logic on the doc.data().
in this case: push all "hashtag1" to the results
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
setState(() {
_allResults.add(doc.data()["hashtag1"]);
});
});
Update Suggested code block
Future<String> getusers() async {
var firestore = FirebaseFirestore.instance;
List listOfIds = [];
QuerySnapshot qn= await firestore
.collection('videos')
.get();
for (var doc in qn.docs) {
setState(() {
_allResults.add(doc.data()["hashtag1"]);
});
}
});
});
if (!mounted) return "Error loading";
searchResults();
return "Complete";
}

yea check out this. if any error, let me no because i am not on system
List<QueryDocumentSnapshot> _allResults =[]
QuerySnapshot qn = await firestore.collection('videos').get();
if (!mounted) return;
setState(() {
_allResults = qn.docs;
});

UPDATE
This line states that the _resultList is a List of documents, and you want to access all the hashtags from it, because you have the for-loop, which goes until it reaches the length of _resultList, therefore you are getting all the hashtags. If you only want to show the hashtag1, then change this:
content: _resultsList[index].data()[hashtag],
to this:
content: _resultsList[index].data()["hashtag1"],
If you want to have all the documents in this List, use this:
.then((QuerySnapshot querySnapshot) {
_allResults = querySnapshot.docs;
}

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)),
]

type 'List<dynamic>' is not a subtype of type 'QueryDocumentSnapshot'

Why im getting this error
[VERBOSE-2:ui_dart_state.cc(186)] Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'QueryDocumentSnapshot'
#0 _OpenallsinglehashtagsState.getusers
package:wichtigdenyady/homesearchingall/openalldocs.dart:90
<asynchronous suspension>
I dont know actually why im getting this error maybe anyone can help
Heres where the error throws
List<QueryDocumentSnapshot> _allResults = [];
var firestore = FirebaseFirestore.instance;
QuerySnapshot snapshots = await firestore.collection('videos').get();
for (var doc in snapshots.docs) {
_allResults.add(doc.data()["Hashtagsforallvideos"]);
}
I think maybe it comes from data() because this is dynamic? What I trying here is getting the array from firebase that calls Hashtagsforallvideos which every video in videos collection has.
If you need more information please leave a comment
This is my howle code
class Openallsinglehashtags extends StatefulWidget {
static const route = '/Openallsinglehashtags';
final TextEditingController searchinginput;
const Openallsinglehashtags({Key key, this.searchinginput}) : super(key: key);
#override
_OpenallsinglehashtagsState createState() => _OpenallsinglehashtagsState();
}
class _OpenallsinglehashtagsState extends State<Openallsinglehashtags> {
List <QueryDocumentSnapshot>_allResults = [];
List _resultsList = [];
Future resultsLoaded;
bool nosuerfound = false;
String searchresult;
#override
void initState() {
super.initState();
widget.searchinginput.addListener(_onsearchChanged);
setState(() {
nosuerfound = true;
});
}
#override
void dispose() {
widget.searchinginput.removeListener(_onsearchChanged());
super.dispose();
}
#override
void didChangeDependencies() {
widget.searchinginput.text;
resultsLoaded = getVideos();
super.didChangeDependencies();
}
_onsearchChanged() {
setState(() {
nosuerfound = false;
});
searchResults();
}
searchResults() {
var showResults = [];
if (widget.searchinginput.text != "") {
for (var tripsnapshot in _allResults) {
var title3 = DatbaseService.instance
.videosfromsnapshot(tripsnapshot)
.allhashtagsofeveryvideo
.toLowerCase();
if (title3.contains(widget.searchinginput.text.toLowerCase())) {
setState(() {
nosuerfound = true;
});
showResults.add(tripsnapshot);
}
}
} else {
setState(() {
nosuerfound = true;
});
showResults = List.from(_allResults);
}
setState(() {
_resultsList = showResults;
});
}
Future<List<String>> getVideos() async {
List<String> allVideoHastags = [];
var firestore = FirebaseFirestore.instance;
QuerySnapshot snapshots = await firestore.collection('videos').get();
for (QueryDocumentSnapshot videoSnapshot in snapshots.docs) {
List<String> videoHastags =
List.from(videoSnapshot.data()['Hashtagsforallvideos']);
allVideoHastags.addAll(videoHastags);
}
print(allVideoHastags);
_allResults= snapshots.docs;
searchResults();
return allVideoHastags;
}
#override
Widget build(BuildContext context) {
final user = Provider.of<Userforid>(context);
if (nosuerfound == true) {
return ListView.builder(
itemCount: _resultsList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
margin: const EdgeInsets.all(10.0), // Add margin
child: InkWell(
onTap: () {
NavigationService.instance
.navigateToRoute(MaterialPageRoute(builder: (context) {
return Beitragvideo(
_resultsList[index].data()['Hashtagsforallvideos'],
_resultsList[index].data()['Hashtagsforallvideos'],
_resultsList[index].data()['Hashtagsforallvideos'],
);
}));
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
HighlightedMatchesText(
searchString: widget.searchinginput.text,
content:
_resultsList[index].data()['Hashtagsforallvideos']),
],
),
),
);
},
);
} else {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 30, 0, 0),
child: Center(
child: Container(
child: Text(
"No Hashtag found",
style: TextStyle(fontSize: 16),
)),
),
What I do is I using a searchfield where user can search hashtags. And that hashtags are from the arrays of each video . So what I want is when user typed one hashtags only showing him this hashtags that he enter and not all hashtags of one video and also not all hashtags of the howle list . and if he not searching I just add all hashtags and user can only search in that list if theres no hashtag found then I print no hashtags is found and if any hashtag of that list contains user input I show him these hashtags. HighlightedMatchesText is just a class where I bold these letters that contains user input .
I think we dicussed this on another StackOverflow question. It is not possible to accomplishe what you want. A List<QueryDocumentSnapshot> representing your hashtags because a QueryDocumentSnapshot can only represent a whole firestore document and not just parts/Fields of it.
That means you have 2 options:
Get the whole documents in a list and using QueryDocumentSnapshot like this:
(Your can still read the hastags from the snapshots)
Future<List<QueryDocumentSnapshot>> getVideos() async {
List<QueryDocumentSnapshot> _allResults = [];
QuerySnapshot snapshots = await _firestore.collection('videos').get();
for (QueryDocumentSnapshot videoSnapshot in snapshots.docs) {
_allResults.add(videoSnapshot);
}
return _allResults;
}
Get a List of Strings having all the hashtags without the QueryDocumentSnapshot like this:
Future<List<String>> getHashtags() async {
List<String> allVideoHastags = [];
QuerySnapshot snapshots = await _firestore.collection('videos').get();
for (QueryDocumentSnapshot videoSnapshot in snapshots.docs) {
List<String> videoHastags =
List.from(videoSnapshot.data()['Hashtagsforallvideos']);
allVideoHastags.addAll(videoHastags);
}
return allVideoHastags;
}
If remember correct from your previous question Hashtagsforallvideos was a List<String>?
If this is correct then the values being added to the _allResults will not be QueryDocumentSnapshot as defined by the assignment but rather String, hence the error, _allResults expects a Firebase QueryDocumentSnapshot, but you are adding Strings to the List.
Do let me know if this is true.

Flutter: Items from Firestore Not Shown in the GridView until Refresh

I have a flutter app here connected to firestore backend and I face a strange behaviour here.
When the app starts or I do hot restart the items fetched from the database are shown for one second and then disappear again and I have to apply RefreshIndicator and drag the screen down to refresh the products and let them appear again.
Here is my code to fetch items:
Future<void> fetchitems() async {
try {
final List<Product> loadedProducts = [];
final response = await Firestore
.instance
.collection("products")
.getDocuments();
response.documents.forEach((element) {
loadedProducts.add(Product(
id: element.documentID,
title: element.data['title'],
description: element.data['description'],
price: element.data['price'],
imageUrl: element.data['imageUrl']
));
});
_items = loadedProducts;
notifyListeners();
} catch (error) {
print(error);
}
}
Here is GridView and how it receives items:
Widget build(BuildContext context) {
final productsData = Provider.of<Products>(context);
final products = productsData.items;
return GridView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: products.length,
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: products[i],
child: ProductItem(),
),
Here is where I call the GridView:
class _ProductsOverviewScreenState extends State<ProductsOverviewScreen> {
var _isIntit = true;
var _isLoading = false;
Future<void> _refreshProducts(BuildContext context) async {
await Provider.of<Products>(context).fetchAndSetProducts();
}
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
if (_isIntit) {
setState(() {
_isLoading = true;
});
Provider.of<Products>(context).fetchitems().then((_) {
setState(() {
_isLoading = false;
});
});
}
_isIntit = false;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: _isLoading ? Center(child: CircularProgressIndicator(), ) : RefreshIndicator(
onRefresh: () => _refreshProducts(context),
child: ProductsGrid()),
);
}
}
final productsData = Provider.of<Products>(context, listen: false);
listen: false ensure that build will not retrigger again in the same build.

List is showing null when should show the data from firestore

I want to search in firestore data but the list is not showing me data from firestore. I want to return my list with firestore data but it is not showing me and i want to search in list data according to my choice.
Please tell me where I am wrong?
class serachDeligat extends SearchDelegate<String> {
Firestore _firestore = Firestore.instance;
String ref = 'items';
Future<List<DocumentSnapshot>> getSearch() async =>
await _firestore.collection(ref).getDocuments().then((snaps) {
return snaps.documents;
});
List<Map> search = <Map>[];
Future getDocs() async {
search = await (await getSearch()).map((item) => item.data).toList();
return search;
}
#override
void initState() {
getDocs();
}
#override
List<Widget> buildActions(BuildContext context) {
// TODO: implement buildActions
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
print('$search');
},
),
];
}
#override
Widget buildLeading(BuildContext context) {
// TODO: implement buildLeading
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
return Container(
child: Center(),
);
}
#override
Widget buildSuggestions(BuildContext context) {
return Container();
}
}
I will share what I did in one of my projects which had the same problem.I tried many ways but all of them end up with a null.Then I change my function with the help of StreamSubscription.This will change the data even after retrieving from firestore since it maintains a connection with firestore.I loaded data inside the initstate.
StreamSubscription<QuerySnapshot> dailyTaskSubscription;
ProgressDialog progressDialog;
List<ScheduleTask> mondayTaskList = List();
#override
void initState() {
super.initState();
mondayTaskList = List();
dailyTaskSubscription?.cancel();
dailyTaskSubscription = scheduleService
.getDailyTaskList(studentId, 'monday')
.listen((QuerySnapshot snapshot) {
final List<ScheduleTask> tasks = snapshot.documents
.map((documentSnapshot) =>
ScheduleTask.fromMap(documentSnapshot.data))
.toList();
});
}
here is the answer to my question
I should use the await in my onPressed
class serachDeligat extends SearchDelegate<String>{
Firestore _firestore = Firestore.instance;
String ref = 'items';
Future<List<DocumentSnapshot>> getSearch() async =>
await _firestore.collection(ref).getDocuments().then((snaps) {
return snaps.documents;
});
List<Map> search = <Map>[];
Future getDocs() async {
search=await
(await getSearch()).map((item) => item.data).toList();
return search;}
#override
void initState() {
getDocs();
super.query;
}
List<Map> searchAi = <Map>[];
#override
List<Widget> buildActions(BuildContext context) {
// TODO: implement buildActions
return[IconButton(
icon: Icon(Icons.clear),
onPressed: ()async {
searchAi=await getDocs();
print(searchAi);
},),];}
#override
Widget buildLeading(BuildContext context) {
// TODO: implement buildLeading
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: (){
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
return Container(
child: Center(),);}
#override
Widget buildSuggestions(BuildContext context) {
return Container();}}

Pagination in Flutter with Firebase Realtime Database

I am trying to paginate in Flutter with firebase realtime databse.
I have tried this in Firestore and it works fine there but I want this with realtime database.
I am fetching data for the first time like this.
Widget buildListMessage() {
return Flexible(
child: StreamBuilder(
stream: _firebase.firebaseDB
.reference()
.child("chats")
.child("nsbcalculator")
.orderByChild('timestamp')
.limitToFirst(15)
.onValue,
builder: (context, AsyncSnapshot<Event> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor)));
} else {
if (snapshot.data.snapshot.value != null) {
listMessage = Map.from(snapshot.data.snapshot.value)
.values
.toList()
..sort(
(a, b) => a['timestamp'].compareTo(b['timestamp']));
if (lastVisible == null) {
lastVisible = listMessage.last;
listMessage.removeLast();
}
}
return ListView.builder(
...
);
}
},
),
);
}
After that to paginate I am using a listener with ScrollController
void _scrollListener() async {
if (listScrollController.position.pixels ==
listScrollController.position.maxScrollExtent) {
_fetchMore();
}
}
and finally
_fetchMore() {
_firebase.firebaseDB
.reference()
.child("chats")
.child("nsbcalculator")
.orderByChild('timestamp')
.startAt(lastVisible['timestamp'])
.limitToFirst(5)
.once()
.then((snapshot) {
List snapList = Map.from(snapshot.value).values.toList()
..sort((a, b) => a['timestamp'].compareTo(b['timestamp']));
if (snapList.isNotEmpty) {
print(snapList.length.toString());
if (!noMore) {
listMessage.removeLast();
//Problem is here.....??
setState(() {
listMessage..addAll(snapList);
});
lastVisible = snapList.last;
print(lastVisible['content']);
}
if (snapList.length < 5) {
noMore = true;
}
}
});
}
Its working fine as realtime communication but when I try to paginate in _fetchMore() setState is called but it refreshes the state of whole widget and restarts the StreamBuilder again and all data is replaced by only new query. How can I prevent this??
Calling setState will redraw your whole widget and your list view. Now, since you supplying the steam that provides the first page, after redraw it just loads it. To avoid that you could use your own stream and supply new content to it. Then your StreamBuilder will handle the update automatically.
You need to store the full list of your items as a separate variable, update it and then sink to your stream.
final _list = List<Event>();
final _listController = StreamController<List<Event>>.broadcast();
Stream<List<Event>> get listStream => _listController.stream;
#override
void initState() {
super.initState();
// Here you need to load your first page and then add to your stream
...
_list.addAll(firstPageItems);
_listController.sink.add(_list);
}
#override
void dispose() {
super.dispose();
}
Widget buildListMessage() {
return Flexible(
child: StreamBuilder(
stream: listStream
...
}
_fetchMore() {
...
// Do your fetch and then just add items to the stream
_list.addAll(snapList);
_listController.sink.add(_list);
...
}
Try this one
pagination for RealTime list
class FireStoreRepository {
final CollectionReference _chatCollectionReference =
Firestore.instance.collection('Chat');
final StreamController<List<ChatModel>> _chatController =
StreamController<List<ChatModel>>.broadcast();
List<List<ChatModel>> _allPagedResults = List<List<ChatModel>>();
static const int chatLimit = 10;
DocumentSnapshot _lastDocument;
bool _hasMoreData = true;
Stream listenToChatsRealTime() {
_requestChats();
return _chatController.stream;
}
void _requestChats() {
var pagechatQuery = _chatCollectionReference
.orderBy('timestamp', descending: true)
.limit(chatLimit);
if (_lastDocument != null) {
pagechatQuery =
pagechatQuery.startAfterDocument(_lastDocument);
}
if (!_hasMoreData) return;
var currentRequestIndex = _allPagedResults.length;
pagechatQuery.snapshots().listen(
(snapshot) {
if (snapshot.documents.isNotEmpty) {
var generalChats = snapshot.documents
.map((snapshot) => ChatModel.fromMap(snapshot.data))
.toList();
var pageExists = currentRequestIndex < _allPagedResults.length;
if (pageExists) {
_allPagedResults[currentRequestIndex] = generalChats;
} else {
_allPagedResults.add(generalChats);
}
var allChats = _allPagedResults.fold<List<ChatModel>>(
List<ChatModel>(),
(initialValue, pageItems) => initialValue..addAll(pageItems));
_chatController.add(allChats);
if (currentRequestIndex == _allPagedResults.length - 1) {
_lastDocument = snapshot.documents.last;
}
_hasMoreData = generalChats.length == chatLimit;
}
},
);
}
void requestMoreData() => _requestChats();
}
ChatListView
class ChatView extends StatefulWidget {
ChatView({Key key}) : super(key: key);
#override
_ChatViewState createState() => _ChatViewState();
}
class _ChatViewState extends State<ChatView> {
FireStoreRepository _fireStoreRepository;
final ScrollController _listScrollController = new ScrollController();
#override
void initState() {
super.initState();
_fireStoreRepository = FireStoreRepository();
_listScrollController.addListener(_scrollListener);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Flexible(
child: StreamBuilder<List<ChatModel>>(
stream: _fireStoreRepository.listenToChatsRealTime(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
controller: _listScrollController,
shrinkWrap: true,
reverse: true,
itemBuilder: (context, index) {
...
}
);
}
)
),
);
}
void _scrollListener() {
if (_listScrollController.offset >=
_listScrollController.position.maxScrollExtent &&
!_listScrollController.position.outOfRange) {
_fireStoreRepository.requestMoreData();
}
}
}
ChatModel Class
class ChatModel {
final String userID;
final String message;
final DateTime timeStamp;
ChatModel({this.userID, this.message, this.timeStamp});
//send
Map<String, dynamic> toMap() {
return {
'userid': userID,
'message': message,
'timestamp': timeStamp,
};
}
//fetch
static ChatModel fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return ChatModel(
userID: map['userid'],
message: map['message'],
timeStamp: DateTime.fromMicrosecondsSinceEpoch(map['timestamp'] * 1000),
);
}
}

Resources