how to reduce cloud firestore reads? - firebase

i have a search bar that receives a query string from the user, if the query string is empty, like i just initiated the search operation, it fetches all documents in posts collection...if user starts typing inside the search bar...it takes the typed text and compare it to a field inside each document in firestore collection and if it matches...its retrieves matched documents and it and previews them in screen, if the user deleted his typed query string...so the query string is empty again...so it fetches all documents again...i use provider package to conduct the query string to database fetch function like so:
Provider:
import 'package:flutter/foundation.dart';
class QueryStringProvider extends ChangeNotifier {
String _queryString = '';
String getQueryString() => _queryString;
updateQueryString(String userQueryString) {
_queryString = userQueryString;
notifyListeners();
}
}
the database function:
Stream<QuerySnapshot> filterAllPosts(String query) {
return query.isEmpty
? _postsCollection.snapshots()
: _postsCollection
.where('postSubtitles', arrayContains: query)
.snapshots();
}
the following is a stream builder wrapped inside a Consumer<QueryStringProvider>, the Streambuilder receives all data retrieved from database fetch function, and populate the data according to my preferred layout structure:
#override
Widget build(BuildContext context) {
return Container(
child: Consumer<QueryStringProvider>(
builder: (context, data, _) {
return StreamBuilder<QuerySnapshot>(
// stream: DatabaseService().listenToAllGigs(),
stream: DatabaseService().filterAllGigs(data.getQueryString()),
builder: (context, snapshot) {
return !snapshot.hasData
? Center(child: Text(''))
: snapshot.data.documents.length > 0
? ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot data =
snapshot.data.documents[index];
Map getDocData = data.data;
return GestureDetector(
child: PostItem(
appointed: getDocData['appointed'],
appointedUserFullName:
getDocData['appointedUserFullName'],
postId: getDocData['gigId'],
postTitle: getDocData['postTitle'],
postSubtitles: getDocData['postSubtitles'],
postBody: getDocData['postBody'],
// onDeleteItem: () => model.deleteGig(index),
),
);
})
: Center(
child: Text(
'No Posts matching your criteria',
style: TextStyle(fontSize: 16),
));
},
);
},
),
);
}
i have three questions about how to reduce cloud reads with the above code:
1) is there any problems that i fetch all documents when i initiate the search operation and the query string is empty?
2)when users starts typing inside the search bar...does this mean that all the fetched data get lost..and we are making new reads again with filtering?...or it just filters what i've already fetched from initiating the search operation from the start?
3) when user deletes the query string and cancels filtering and and query string is now empty...does this mean that i will make a new full read to the cloud collection again...so everytime the users types or deletes the query string i iterate through all the collection again?*
Execuse me if the question is too long...but it is much closer to theoretical concepts rather than code...
any clarifications would be much appreciated...
many thanks.

What I suggest is querying the result only if the user has entered some specific number of characters and all of them are valid.
Like, in my case, I ensure that the user has entered at least 3 characters and all of them are alphabets or numbers.

Related

Flutter Firebase Cloud Firestore get stream of list of documents by their ids

Hey guys I have two top level collections, a user and a tabs collection. Within each user document I have an array field of document ids with each element in the array representing a document in the tabs collection (sort of like a reference or a pointer to that document) and I would like to listen to real time changes in the users/document with the list of ID's and to listen for changes to the corresponding documents in the tabs collection.
Below is my code so far and there are two issues with it. The stream isn’t working client side - I’m not receiving an updated snapshot when I update the document in cloud firestore. Also in order for me to get a list of tabIds I am currently making a query to cloud firestore which returns a stream however, the whereIn operator only accepts a list of objects so I’m not to sure how to handle that.
data model screenshot
Stream<List<Tab>> get tabs {
var tabIds = _db.collection('users').doc(user.uid).snapshots().where((event) => event.get('tabs'));
return _db
.collection('tabs')
.where(FieldPath.documentId, whereIn: tabIds)
.snapshots()
.map((list) =>
list.docs.map((doc) => Tab.fromJson(doc.data())).toList());
}
You can't directly listen to documents based on their ids, but you can add a field called docId (the value should be the id of the document) to each document then listen to collection with this where condition.
List listOfDocIds = []; // this is the list of your docIds
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('users')
.where('docId', whereIn: listOfDocIds),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.hasError) return Text('Something went wrong');
if (snapshot.connectionState == ConnectionState.waiting)
return CircularProgressIndicator();
List<Map<String, dynamic>> data =
snapshot.data.docs.map((e) => e.data()).toList();
print(data);
// you can build your widget here
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, i) {
return ListTile(
title: Text(data[i]['name']),
// TODO: change 'name' to name of field in users document
);
},
);
},
),

Flutter using StreamBuilder with setState updates

I'm using a StreamBuilder inside a build method to retrieve user info from firestore and right after it is displayed with a Listview.builder.
Once i have retrieved the user info, i call another API with the user id's to retrieve some other info i would like to display in the list. This gives me a Future for each user, once the future has been "fulfilled" (inside Future.then()), then i save the data and call SetState(); to rebuild the list with the new data.
The problem is once setState() is called the Build method is rerun which gives me the users again, and this causes an endless loop of retrieving the users and the data from the second API.
This seems like a typical scenario but i don't know how to solve this with StreamBuilder. My previous solution retrieved the users in initState so it was not recalled endlessly.
Any tips?
"getOtherAPIData" loops the users retrieved and adds data "distance" to the list, and the list is sorted based on the user with the most/least distance.
Code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<List<IsFriend>>(
stream: viewModel.isFriendStream(),
builder: (_, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
List<IsFriend> friends = snapshot.data;
if (friends == null || friends.isEmpty) {
return Scaffold(
body: Center(
child: Text("Friend list is empty :("),
)
);
} else {
userList = friends;
getOtherAPIData();
return getListview(userList);
}
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
)
);
}
),
..
..
void getOtherAPIData() async {
for (IsFriend friend in userList) {
await fitbitServ.getData(friend.user.uid).then((fitData) {
setState(() {
setDistanceOnUser(fitData.distanceKm, friend.user.uid);
totalDistance += fitData.distanceKm;
sortUsers(userDataList);
userData[friend.user.uid] = fitData;
});
});
}
}
You should never call a method that starts loading data from within your build method. The normal flow I use is:
Start loading the data in the constructor of the widget.
Call setState when the data is available.
Render the data from the state in build.

flutter: Retrieving multiple document id present inside a collection of firebase

I want to get the id of every document in a collection. i tried this code but it return a single document id unlimited times. Can you please suggest a correction or a alternative
getrequest() {
linkref.document(uuid).
collection("Requests").getDocuments().then((value) async{
value.documents.forEach((doc) {
user.add(doc.documentID);
});},);
return ListView.builder(
itemBuilder: (BuildContext cntxt, int index){
return Text(user[index]);
});
}
there is a collection inside a document and inside this collection i have other document. i want to retrieve all documents id
This is screenshot of my firestore
Currently, you have just one document in firestore. I would suggest you add multiple documents and then test this command.You can use snapshot to get multiple documents as suggested by Sandeep using
Firestore.instance
.collection('Links')
.document(docID)
.collection('Requests')
.snapshots();
You can retrieve multiple documents with one request by querying documents in a collection. By default, Cloud Firestore retrieves all documents that satisfy the query in ascending order by document ID, but you can order and limit the data returned. To retrieve the documents conditionally using where() and then use get
More can be found in the documentation here
To use ListView builder, I would suggest using StreamBuilder something like this:
StreamBuilder<DocumentSnapshot>(
stream: Firestore()
.collection('homepage')
.document(widget.user.uid)
.collection('h')
.document(todaysDate())
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data != null) {
snapshot.data.data.forEach((index, individualDetail) {
cardDetails[index] = individualDetail;
});
}
return cardDetails == null
? CircularProgressIndicator()
: ListView.builder(
itemBuilder: (context, index) {
return HomepageCards(
user: widget.user,
cardDetails:
cardDetails[cardDetails.keys.toList()[index]],
);
},
itemCount: (cardDetailKeys == null
? 0
: cardDetailKeys.length),
);
},
)
This is a snippet from my code but the StreamBuilder would look similar for you too.
Firestore.instance
.collection('Links')
.document(docID)
.collection('Requests')
.snapshots();

Flutter with firebase trying to access the current user on a different screen or widget than my login screen

So I have a dart file for my login screen and then a different home file. I'm trying to access the user id in the home file to get the users unique Cloud Firestore document. However whenever I call getUser() it says Future is not a subtype of type 'String'. Does that mean that for some reason the Future isn't being unwrapped to return the uid String?
getUser() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user.uid;
}
Here is where I call get user in a different dart file than the authentication but in the same file as my getUser() function.
return StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance.collection('watchlists').document(getUser()).snapshots(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot){
if (snapshot.hasError) {
return new Center(
child: Text('Error: ${snapshot.error}'),
);
}
if (!snapshot.hasData) {
return new Center(
child: CircularProgressIndicator(),
);
}
else {
var documents = snapshot.data.data;
var vals = documents.values;
List<WatchListItem> watchListItems = [];
for (int x = 0; x < vals.length; x++) {
watchListItems.add(
WatchListItem(
symbol: vals.elementAt(x).toString(),
price: 190.79,
),
);
}
return new Center(
child: ListView(
children: watchListItems,
),
);
}
},
);
When I try getUser().toString() the error I get is that the getter 'values' was called on null (I am trying to get database values). Can the current user only be accessed at the time of authentication? Or can it be accessed anywhere in the app at any time as long as a user is logged in? A workaround I see is to get the uid at login and then pass it as a parameter to every new Widget I go to. I'm having a really tough time with this because somehow Google barely has any documentation on using their own Flutter with their Firebase.
getUser() returns a Future<String> while document() accepts a String as an argument:
/// so that the resulting list will be chronologically-sorted.
DocumentReference document([String path]) =>
DocumentReference._(_delegate.document(path), firestore);
Therefore the best place to call getUser() is inside initState. Inside initState you can do:
String userId = await getUser();
Then use the userId in document()

How to access query from itemBuilder?

I am make random chat app use FirestoreAnimatedList (FirebaseAnimatedList for Firestore).
I want show user avatar only one time if same user send many message so not have same avatar show many time.
My solution is use index for check if last message send by same user.
So I need check like :
if (snapshot[index - 1][‘user’] == user) {
return true;
}
But my snapshot is DocumentSnapshot so cannot call [index - 1].
Maybe I must get list from query?
How to access so can call index on list and query like this?
Thanks!
Here example:
body: FirestoreAnimatedList(
query: firestore.collection('messages').snapshots(),
itemBuilder: (
BuildContext context,
DocumentSnapshot snapshot,
Animation<double> animation,
int index,
) {
return FadeTransition(
opacity: animation,
child: MessageItem(
index: index,
document: snapshot,
onTap: _removeMessage,
),
);
},
You can just store the users in a Map.
In your widget's class, you will have to create a variable to hold the map:
final Map<int, dynamic> users = {};
Then, you can populate the map in the itemBuilder callback:
itemBuilder: (
BuildContext context,
DocumentSnapshot snapshot,
Animation<double> animation,
int index,
) {
users[index] = snapshot['user']; // added line
return FadeTransition(
opacity: animation,
child: MessageItem(
index: index,
document: snapshot,
onTap: _removeMessage,
),
);
},
Now, you can do what you wanted:
if (users[index - 1] == user) {
return true;
}

Resources