I'm trying to add pagination in images data that I'm retrieving from firebase using Firebase Storage. I have 10 images there and I want to display 2 at a time in RecyclerView and when the user scrolls down to end vertically, it loads the next 2 until all the images are displayed, I have also read some documentation of Firebase where it was mentioned to use storage.list(int max results) method but with that, it only shows the number of results that I pass in the method for instance if I pass 2 it shows 2 images only, and I can't load anymore. I've found one method too on the official documentation i.e below: https://firebase.google.com/docs/storage/android/list-files
public void listAllPaginated(#Nullable String pageToken) {
FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference listRef = storage.getReference().child("files/uid");
// Fetch the next page of results, using the pageToken if we have one.
Task<ListResult> listPageTask = pageToken != null
? listRef.list(100, pageToken)
: listRef.list(100);
listPageTask
.addOnSuccessListener(new OnSuccessListener<ListResult>() {
#Override
public void onSuccess(ListResult listResult) {
List<StorageReference> prefixes = listResult.getPrefixes();
List<StorageReference> items = listResult.getItems();
// Process page of results
// ...
// Recurse onto next page
if (listResult.getPageToken() != null) {
listAllPaginated(listResult.getPageToken());
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// Uh-oh, an error occurred.
}
});
}
I'm confused about how to use it, I don't know where I can get a page token from in order to provide a reference to open the next page
The thing is I have 200 images in my Firebase Storage and I directly want to apply pagination to the URLs that I'm retrieving from Firebase Storage.
To achieve that, you have two options. The first one would be to use StorageReference#listAll() method which:
List all items (files) and prefixes (folders) under this StorageReference.
And according to the official documentation regarding how to list files with Cloud Storage on Android, please note that:
Cloud Storage for Firebase allows you to list the contents of your Cloud Storage bucket. The SDKs return both the items and the prefixes of objects under the current Cloud Storage reference:
So you have to explicitly differentiate that (items vs. prefixes) in your application code and provide your own pagination algorithm.
The second option that you have, and the simplest one, in my opinion, would be to store the image URLs in the Firestore and implement the pagination as explained here:
Paginate data with query cursors
Related
I'm working on a way to pass multiple files (in one go) to the firebase storage. To do so, I'm using the putFile() method. This method works perfectly with 1 file, but I'm struggling to get it to work for multiple files (that are stored in an ArrayList) at a time. Below is the logic I have used (I know it's wrong. Any help is much appreciated):
Store files that I want to pass to the storage folder in an ArrayList
Using a for-loop, iterate through the said ArrayList and put items at [i] --changes
In an OnSuccessListener for 2, downloadUrl
Initialise an ArrayList to store the downloadUrl result
In an OnSuccessListener for 3, add the downloaded url from 3 into an updated ArrayList, only if the current position (i) is == ArrayList.size-1. I use this so that there aren't any duplicated instances every single time that the method is called (i.e. when the loop runs), but the method is only called when the first 4 (above) are done
Call a method that uploads the ArrayList generated from 5 (+ other things) to a rtdb
Below is the code I have written:
for (i in 0 until arrayList.size){
storageRef.putFile(arrayList[i])
.addOnSuccessListener {
Log.d("TAG", "Successfully uploaded file: ${it.metadata?.path}")
storageRef.downloadUrl.addOnSuccessListener { it ->
it.toString()
Log.d("TAG", "File location: $it")
updatedArrayList.add(it.toString())
if (i == arrayList.size){
//method that creates an instance of an object and passes data to the rtdb (firebase)
createInstanceUploadToFirebase(id, updatedArrayList)
}
}
}
}
The issue with the above is that I now get the following error when I try to access (load the file using the generated downloadUrl) the files (apart from the last one) using the downloadUrl and only seem to have access to the item at the very last index:
{
"error": {
"code": 403,
"message": "Permission denied."
}
}
Any help is appreciated(:
The issue wasn't with the logic, it was to do with the security rules for the storage. This is how I resolved the problem:
Update storage security rules to:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow write: if request.auth != null;
allow read;
}
}
}
Reason for the error detailed in the question:
The default read and write rules only allow users to read from the storage if they're authenticated. Changing this by using the above storage rules should fix the issue.
I'm currently loading an undefinite number of online images in a TableLayout. To do so I use a loadimage() function that fetch the image url and then load it like so (not actual code):
for(i=0;i<numberofimagetoload;i++)
{
loadimage(i);
}
public void loadimage(int imagenumber)
{
String url = serverimage.getURL(imagenumber)
ImageView.loadImageFromURL(url)
}
But images are loading in complete disorder, mainly because of the fetching time.
My question is: "How can I wait for the loadimage function to complete before moving on?"
Try Linked hash set. It prevents duplicate values and maintains the insertion order.
ArrayList can also help.
For network call use retrofit. It returns an object in response.
This is what my Firestore database looks like:
I have a RecyclerView which displays a list of posts. But I am not able to retrieve a document with its subcollections and put it inside an object. When I submit a list of posts in my RecyclerView, I want each post to already have its Likes and Comments.
I use AsyncTask to do the background work of querying all the posts of the user. After the AsyncTask is done, it will submit the List of posts to the RecyclerView. (Still doesn't work)
In the code below, I'm calling all the Posts of the user and then getting the "Likes" of each post.
I have put a while loop inside each post so that I can wait for it to finish querying the Likes before proceeding to the next Post snapshot
doInBackground()
final List<Post> postList = new ArrayList<>();
// -- GET all the posts of the user
feedsCollection.document(documentID).collection("Posts").get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for(final DocumentSnapshot documentSnapshot1 : queryDocumentSnapshots){
final Post post = documentSnapshot1.toObject(Post.class);
// Finish getting the likes&comments before proceeding
while(post.isStillLoading()){
// -- GET Likes of each post
db.collection("FeedsCollection").document(documentID).collection("Posts")
.document(documentSnapshot1.getId()).collection("Likes").get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
List<Like> likeList = new ArrayList<>();
for(DocumentSnapshot likeSnapshot : queryDocumentSnapshots){
Like like = likeSnapshot.toObject(Like.class);
likeList.add(like);
}
post.setLikes(likeList);
post.setStillLoading(false);
}
});
// -- GET comments code
}
postList.add(post);
}
}
});
return postList;
onPostExecute()
protected void onPostExecute(List<Post> postList) {
super.onPostExecute(postList);
adapter.submitData(postList);
}
These are the things I've tried:
I tried having multiple
viewtypes in my recyclerview, so that if the list is still empty, it
displays "No items". And then when the AsyncTask finish, it will
re-submit a list to the recyclerview.
I tried to use FirestoreRecyclerAdapter but I've read that its queries are shallow so it can't get the Likes and Comments subcollections of each post document altogether.
Any other workaround for this?
But I am not able to retrieve a document with its subcollections and put it inside an object.
There's no way you can achieve this. The queries in Firestore are shallow, so it is allowed to only get documents from the collection that the query is run against. Furthermore, there is no way you can get a document together with the nested subcollections in a single go. You can get the document and then run separate queries for each and every subcollection.
Another possible solution might be to use Firebase Realtime Database, where when you download a node, you download it together with all the data that exist beneath that node.
I use AsyncTask to do the background work of querying all the posts of the user.
The Cloud Firestore client already runs all network operations in a background thread. This means that all operations take place without blocking your main thread. Putting it in an AsyncTask does not give any additional benefits.
If you have data in multiple collections, even nested collections, you will have to perform multiple queries to get all that data. It won't be possible with just a single query. You can certainly implement what you want, it will just be significantly more complex to do so.
It will require one query to get all the documents in PostsCollection, then more queries for each of the subcollections nested under Comments and Likes for each of the posts.
Switching to Realtime Database as Alex suggests might not be a good idea. You will lose a lot of more complex querying capability just to get deep queries, and you might not even want fully deep queries all the time, as that could be more expensive overall.
I'm trying to write messenger app using Firebase.
In database I have a few entries, which are User.class objects. I'm trying to write function which can download User object from database. I though that it'd be better to build separate class (UserManager) for this task, because I don't like making mess in code. But there is a problem, because in onCreate method I need to use User object to download some additional info from database to create conversation list, so downloading user from server should be done before that. Also if user is not in database, it should create and push User to database using FirebaseAuth (I've got that working).
Should I build class extending AsynchTask, and there put downloading user, and then updating UI with the data downloaded after user ?
How do I know if the user was already downloaded. Probably I should build some listener but I don't know how to do that.
Additional question:
If I use this reference with value listener, do i get a user object or some value from inside of the object?
DatabaseReference userReference = FirebaseDatabase.getInstance().getReference().child("users/" + mUserID);
Here is my database:
Each entry key is userID from FirebaseAuth for easier implementation.
I've been cracking my head on this for a few days and tried different approaches. I'll apriciate any help. I think, that some code or a scheme would be a huge help.
How do I know if the user was already downloaded?
You can add a flag to each user with the value of false and once you have downloaded the user object, to set the value to true but this is not how things are working with Firebase. You cannot know when a user from the database is completed downloaded becase Firebase is a realtime database and getting data might never complete. That's why is named a realtime database because in any momemnt the data under that User object can be changed, properties can be added or deleted.
You can use a CompletionListener only when you write or update data and you'll be notified when the operation has been acknowledged by the Database servers but you cannot use this interface when reading data.
If I use this reference with value listener, do i get a user object or some value from inside of the object?
If the value that you are listening to is a User object, then you'll get a User object. If the value is another type of object, which can also be a String (which is also an object) then you'll get that type of object, which can also be a String object. Remember, that only the keys in a Firebase database are always strings.
Maybe this part of my code will help you figure out:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("users")
.child(mUserID);
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, "DatabaseError: " + databaseError.getMessage());
}
});
Here is my data structure:
I have an ios app that is attempting to access data from Cloud Firestore. I have been successful in retrieving full documents and querying for documents. However I need to access specific fields from specific documents. How would I make a call that retrieves me just the value of one field from Firestore in swift? Any Help would be appreciated.
There is no API that fetches just a single field from a document with any of the web or mobile client SDKs. Entire documents are always fetched when you use getDocument(). This implies that there is also no way to use security rules to protect a single field in a document differently than the others.
If you are trying to minimize the amount of data that comes across the wire, you can put that lone field in its own document in a subcollection of the main doc, and you can request that one document individually.
See also this thread of discussion.
It is possible with server SDKs using methods like select(), but you would obviously need to be writing code on a backend and calling that from your client app.
This is actually quite simple and very much achievable using the built in firebase api.
let docRef = db.collection("users").document(name)
docRef.getDocument(source: .cache) { (document, error) in
if let document = document {
let property = document.get(field)
} else {
print("Document does not exist in cache")
}
}
There is actually a way, use this sample code provided by Firebase itself
let docRef = db.collection("cities").document("SF")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let property = document.get('fieldname')
print("Document data: \(dataDescription)")
} else {
print("Document does not exist")
}
}
I guess I'm late but after some extensive research, there is a way in which we can fetch specific fields from firestore. We can use the select keyword, your query would be somthing like (I'm using a collection for a generalized approach):
const result = await firebase.database().collection('Users').select('name').get();
Where we can access the result.docs to further retrieved the returned filtered result. Thanks!
//this is code for javascript
var docRef = db.collection("users").doc("ID");
docRef.get().then(function(doc) {
if (doc.exists) {
//gives full object of user
console.log("Document data:", doc.data());
//gives specific field
var name=doc.get('name');
console.log(name);
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});