I have a Flutter project that uses Cloud Firestore. In one of my widgets, I have a StreamBuilder that ideally reads snapshots from the database. In theory, my API for reading from a remote server should be abstract enough to swap Firestore out with a different implementation.
class Database {
Stream<QuerySnapshot> get snapshots => Firestore.instance.collection('entires').snapshots();
Stream<List<String>> get entries => snapshots.map((snapshot) => snapshot.documents.map((document) => document.data['name']).toList());
}
If my StreamBuilder uses snapshots, then AsyncSnapshot<QuerySnapshot> has data (hasData returns true).
If my StreamBuilder uses entries, then AsyncSnapshot<List<String>> will never have data (hasData returns false)---even if I successfully printed out what the data is (my return result is a populated list of strings).
I hope to keep my Database interface free of Firestore. So, my question is: why does StreamBuilder's AsyncSnapshot return nothing even if I have data?
It seems that the issue revolves around snapshot() sending data off immediately. I managed to create a work around by creating a class that wraps my Firestore collection.
This wrapper has its own StreamController<T> where T is the non-Firestore (abstract across multiple backend implementations) data type that I want to return.
The wrapper will listen on the necessary Firestore snapshots on construction. The latest snapshot result is cached. My StreamBuilder can then use both the internal StreamController.stream and the cached snapshot data for its construction.
StreamBuilder(
stream: _wrapper.stream,
initialData: _wrapper.latestData,
)
This works for now, but has the side effect of continuously listening on snapshots indefinitely. I'll have to update my wrapper to stop listening on snapshots when not necessary (no subscribers) and re-listen when there are new subscribers, or simply use a mapping of the snapshot stream.
Related
In my app, I am now using a "refresh function" to update a list in Provider. When the user swipe, I call Refreshlist in my provider and with NotifyListeners() it updates my UI just fine. (The UI is linked to the list _myEleves).
I am afraid that users might use this "refresh" button too many times making unnecessary calls and "reads" on firebase and so increasing artificially the number of reads so the costs of Firebase.
Here is the code :
Future<void> refreshEleveList() async {
final DocumentSnapshot<Map<String, dynamic>> docInfo =
await FirebaseFirestore.instance
.collection('familyAccounts')
.doc(_accountEmail.toLowerCase())
.get();
_mesEleves = (docInfo['mesEleves'] as List<dynamic>)
.map((data) => Eleve.fromMap(data))
.toList();
notifyListeners();
}
I have been reading about STREAMS a lot, but I just can't get it right on how to start this stream, the listening to the changes on Firebase inside my PROVIDER file, so that changes will be made to "_myEleves" list.
What I want to do is that each time a change on firebase happens, it updates my list "_myEleves". Is there a simple way to do this ?
My Provider covers the whole app (I use it in the MAIN file). I thought of adding a StreamProvider, but the thing is I don't want this stream to start until user is authentified etc... and userInfo is first downloaded.
Right now : when user logs in : it downloads from firebase all necessary info, _mesEleves being one of them (This is for a teacher). Whenever a new student joins the group, it modifies firebase and so it should stream down this info into "myEleves" list on the teacher account.
Anybody can help ?
I have seen a few articles written online by people pertaining to be able to do this, but they only tell you how to do this with a specific, controlled list of images where they also know all the filenames beforehand.
There is also this "answer" posted here: Flutter - Get all images from firebase storage which does not actually resolve this issue at all as it suggests a .listAll() method for the recommended plugin, but there is no such method as .listAll() using the suggested plugin.
I need to be able to not know how many images are in Firebase storage, or what they might be called, just to return everything stored there.
UPDATE:
So because Firebase is full of so many limitations that it hardly qualifies as a database at all, it seems we may have to keep the images in Firebase Storage and a reference list of these in a Firebase Realtime Database Document.
I am stuck on the actual implementation of this however, as I am not even sure firstly how best to go about this. What I am attempting to do is store all the Storage image URLs in an array (if this is not the best way to do this, let me know!):
Firebase Structure
collection -> document -> fields
userData profileImages URLs (array)
My first issue is that I don't know how to append new data to the existing array, it seems to just overwrite it each time I add a new item so that I only ever have one string in the array in the database:
Firestore.instance.collection('userData').document('profileImages').updateData({
'URLs': _uploadedFileURL,
});
Then after this I am also not sure how to actually retrieve the full array of data later when I need it:
Container(
child: StreamBuilder(
stream: Firestore.instance.collection('userData').document('profileImages').snapshots(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return LoadingAnimationBasic();
}
if (snapshot.data.data == null) {
return LoadingAnimationBasic();
} else {
return ListView(
shrinkWrap: true,
children: _buildProfileGallery(snapshot),
);
}
},
),
),
And then the function:
_buildProfileGallery(AsyncSnapshot<DocumentSnapshot> snapshot) {
int test = snapshot.data["URLs"].length;
print('URLs in List: ' + test.toString());
return snapshot.data.data.map(???);
}
I have no idea what to put as the parameters of this map, as the hint text is insane:
MapEntry(K2, V2> f(String key, V value)
I can't even begin to guess what this means.
Am I on the right track? Am I on the right planet?
The ability to list files was added to Firebase's client-side SDKs for Android, iOS and JavaScript last year, but has not yet landed in the FlutterFire bindings.
The answer to the question you linked, refers to a pull request on the FlutterFire open-source project that adds this functionality. So while somebody wrote code to allow listing of files in Flutter too, this code hasn't been added to a release so far. So the only way to use that code now, is to build your own version of the FlutterFire library for firebase_storage.
Without the ability to list files in the Firebase Storage API, you'll have to revert back to what everyone did before this feature was added: keeping a list of the files in a secondary location, such as the Firebase Realtime Database or Cloud Firestore.
I am a little confused about the difference between these two. My understanding is that getDocuments is a type of Future and seems to get the entire documents according to the query. while snapshots, on the other hand, is a type of Stream and, correct me if I'm wrong, I think it represents the results of the query? I need a more specific explanation of this issue. I will include some code snippets as an example for more clarification
getDocuments()
getUserById(String userId) async {
return await _firestore.collection("users").where("userId", isEqualTo: userId).getDocuments();
}
snapshots()
getUserById(String userId) async {
return await _firestore.collection("users").where("userId", isEqualTo: userId).snapshots();
}
So what's the difference?
When you call getDocuments(), the Firestore client gets the documents matching the query from the server once. Since this may take some time it returns a Future<QuerySnapshot>.
When you call snapshots() the Firestore client gets the documents, and then keeps watching the database on the server for changes that affect your query. So if document is written in the users collection that affects your query, your code gets called again. So this returns a stream of QuerySnapshot.
In both cases the results for the entire query are in the QuerySnapshot object.
I highly recommend reading the Firestore documentation on getting data once and on listening realtime updates. While they don't contain Flutter examples, the explanation in there applies equally to the Flutter libraries.
getDocuments():
It's used to provide data once. Cloud Firestore contains collections and inside these collections, you have documents that may contain subcollections or fields mapped to a value. To retrieve any of the doc fields to used it in widget this is used.
snapshots():
It will be called on every data change in your document query. For this StreamBuilder must be used to fetch fields as modified.
In short, it will do the job of setState() where it gives you the response for every modification so that UI can be updated.
I am wonder what is the difference between .valueChanges() and .get()
Here is the signatures:
valueChanges(): Observable<T[]>;
get(options?: firestore.GetOptions): Observable<firestore.QuerySnapshot>;
If you take a look at this tow calls they returns the same result:
this.firestore.collection('version').valueChanges().subscribe(x => {
console.log;
});
this.firestore.collection('version').get().subscribe(x => {
console.log;
});
It is seems like in case of .get() you can play with GetOptions: 'server' | 'cache' is there other benefits?
In my particular use case I just want to take the data from the server and disconnect, I want to minimize the number of connections to firebase as much as possible.
get() just fetches data a single time.
valueChanges() allows your code to observe changes that happen to documents over time.
Choose the one that meets the needs of your app. If you don't need to be updated with changes to documents as they happen, then don't use valueChanges().
Neither of these establishes any "connections". All Firestore operations are pipelined over a single connection maintained by the SDK.
get() is a method to fetch data
valueChanges() is a multicasting observable that emits an event every time the value of the control changes, in the UI or programmatically
Difference:
get() can get data only once, whereas valueChanges() is automatically fired whenever something changes and retrieve data (observer) by subscribe()
I'm trying to decide how to structure permissions for an app. Much of this comes down to the question in the title. Specifically, if I call:
const tasks = db.collection('tasks').where('canRead', 'array-contains', firebase.auth().currentUser.uid).get() and retrieve an array of document snapshots, is the data actually transferred for each snapshot, or is the data not transferred until I iterate through that array:
for(let taskSS of tasks.docs) { ... and eventually call .data() with let task = taskSS.data() ?
The reason I ask is if I had a task document with a massive array of users who are allowed to get and list that task, I would not want to transfer all of that data on the initial db.collection('tasks').where('canRead', 'array-contains', firebase.auth().currentUser.uid).get() call.
If the data is not actually transferred until we call either .data() or .get() then I could put all the task data in task.public and then just do let task = taskSS.get('public').
Does someone know when the actual data is transferred and put into memory with firestore? Is the data actually "in" the document snapshot or not transferred until you request that data with .data() or .get()?
The data is transferred from the database to your application when you call get() or onSnapshot(). Each DocumentSnapshot contains all data for the document when you get it. Calling document.data() merely returns a map of the data it already has.