How to get download URL of multiple images uploaded using Android & Firebase - android

I see similar questions on this site but most of the questions are related to fetching the download URL for a single uploaded image. Taking help from those posts, now I can get the download URL of a single image.
But I face a problem when I try to get download URL for multiple images uploaded together. I want to do three things...
1. Select three images
2. Upload them to Firebase Cloud Storage
3. Get the URLs of the uploaded images and save them in an ArrayList.
I can do the first two things successfully, but have not managed to achieve the third thing. When I click the "update" button, all images are perfectly stored in Cloud Storage, but show an error when requesting the download URL of all images.
Here is the code for when I click the "update" button:
upload.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
progressDialog.setMessage("Uploading .... ");
progressDialog.show();
storageReference = FirebaseStorage.getInstance().getReference().child("Pictures");
int uploadCount = 0;
// imageList is an ArrayList<Uri> which holds the address of selected 3 images.
// imageAddress is an ArrayList<String> where I want to save all downloadUrls of images (each url is saved as a string).
// imagePath is a StorageReference
while(uploadCount < imageList.size()) {
Log.d("UploadCount", uploadCount+"");
Uri uri_Image = imageList.get(uploadCount);
imagePath = storageReference.child(uri_Image.getLastPathSegment());
imagePath.putFile(uri_Image).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
#Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
imagePath.getDownloadUrl().addOnSuccessListener(newOnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
Uri downloadUri = uri;
imageAddress.add(downloadUri.toString());
Log.d("ImageAddress Size: ", imageAddress.size()+"");
}
});
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(SignOutActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
progressDialog.dismiss();
}
}); //.............
if(uploadCount == (imageList.size()-1)) {
Log.d("Good", "HELLO HELLO");
Toast.makeText(SignOutActivity.this, "Successfully Uploaded", Toast.LENGTH_LONG).show();
upload.setClickable(false);
progressDialog.dismiss();
}
else {
Log.d("BAD", "NOT HELLO "+uploadCount);
}
uploadCount = uploadCount + 1;
}
}
});
Here is the error:
2020-02-15 17:02:26.945 28207-28735/com.example.practiceapplication E/StorageException: StorageException has occurred.
Object does not exist at location.
Code: -13010 HttpResult: 404
2020-02-15 17:02:26.946 28207-28735/com.example.practiceapplication E/StorageException: {"error": {"code": 404, "message": "Not Found. Could not get object", "status": "GET_OBJECT"}}
java.io.IOException: {"error": {"code": 404, "message": "Not Found. Could not get object", "status": "GET_OBJECT"}}
at com.google.firebase.storage.network.NetworkRequest.parseResponse(com.google.firebase:firebase-storage##19.1.1:433)
at com.google.firebase.storage.network.NetworkRequest.parseErrorResponse(com.google.firebase:firebase-storage##19.1.1:450)
at com.google.firebase.storage.network.NetworkRequest.processResponseStream(com.google.firebase:firebase-storage##19.1.1:441)
at com.google.firebase.storage.network.NetworkRequest.performRequest(com.google.firebase:firebase-storage##19.1.1:272)
at com.google.firebase.storage.network.NetworkRequest.performRequest(com.google.firebase:firebase-storage##19.1.1:286)
at com.google.firebase.storage.internal.ExponentialBackoffSender.sendWithExponentialBackoff(com.google.firebase:firebase-storage##19.1.1:70)
at com.google.firebase.storage.internal.ExponentialBackoffSender.sendWithExponentialBackoff(com.google.firebase:firebase-storage##19.1.1:62)
at com.google.firebase.storage.GetDownloadUrlTask.run(com.google.firebase:firebase-storage##19.1.1:76)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
2020-02-15 17:02:30.712 28207-28207/com.example.practiceapplication D/ImageAddress Size:: 1
It will be very helpful to me if anyone tells me the correction. Thank you

Your code suffers from using a mix of local variables and shared global variables whilst dealing with asynchronous code and for loops.
In the code above, you use the global variables imagePath, imageAddress and imageList inside a for loop which ultimately is the key cause of that Exception.
Code breakdown
When you click the upload button, your code performs the following steps with errors shown in bold:
Gets the first image's URI
Updates the value of imagePath to point at that image's upload location
Starts the upload of the first image
Logs "NOT HELLO 0"
Gets the second image's URI
Updates the value of imagePath to point at that image's upload location
Starts the upload of the second image
Logs "NOT HELLO 1"
Gets the third image's URI
Updates the value of imagePath to point at that image's upload location
Starts the upload of the third image
Logs "HELLO HELLO" and Toasts "Successfully Uploaded" (not actually finished yet)
[a few moments later]
The first image finishes uploading
The download URL of the third image is requested (which throws the StorageException)
The second image finishes uploading
The download URL of the third image is requested (which throws another StorageException)
The third image finishes uploading
The download URL of the third image is requested (and would work correctly)
Fixes
To fix this, the following things must be done:
Use a local variable copy of imageList
Use a local variable for storageReference
Use a local variable for imagePath, and rename to imageRef to accurately reflect it's type
Rename imageAddress to imageAddressList to accurately reflect it's type (recommended)
Remove the while() loop and use a for iterator instead
Disable the upload button immediately instead of at the end
Upload each image and fetch the download URLs in parallel, without conflicting with each other
Only display "Successfully uploaded" or "Upload failed" messages after the uploads have actually completed
Update imageAddressList only once, rather than asynchronously.
To be done:
Handle activity lifecycle changes
Tap into currentUploadTask and bind it to a view dialog/notification to show file upload progress
Update the UI once all the uploads are done
Updated code
Note: This was typed free-hand - expect a few typos.
upload.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
progressDialog.setMessage("Uploading .... ");
progressDialog.show();
upload.setClickable(false); // disable upload button whilst uploading
final StorageReference storageReference = FirebaseStorage.getInstance().getReference().child("Pictures");
final List<Uri> clonedImageList = new ArrayList<>(imageList);
imageList.clear(); // empty old list?
int imageListSize = clonedImageList.size();
List<Task<Uri>> uploadedImageUrlTasks = new ArrayList<>(imageListSize);
for (Uri imageUri : clonedImageList) {
final String imageFilename = imageUri.getLastPathSegment();
Log.d("upload.onClick()", "Starting upload for \"" + imageFilename + "\"...");
StorageReference imageRef = storageReference.child(imageFilename); // Warning: potential for collisions/overwrite
UploadTask currentUploadTask = imageRef.putFile(imageUri);
Task<Uri> currentUrlTask = currentUploadTask
.continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
#Override
public Task<Uri> then(#NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
if (!task.isSuccessful()) {
Log.d("upload.onClick()", "Upload for \"" + imageFilename + "\" failed!");
throw task.getException(); // rethrow any errors
}
Log.d("upload.onClick()", "Upload for \"" + imageFilename + "\" finished. Fetching download URL...");
return imageRef.getDownloadUrl();
}
})
.continueWithTask(new Continuation<Uri, Uri>() { // purely for logging to debug, recommended to remove
#Override
public Task<Uri> then(#NonNull Task<Uri> task) throws Exception {
if (!task.isSuccessful()) {
Log.d("upload.onClick()", "Could not get download URL for \"" + imageFilename + "\"!");
throw task.getException(); // rethrow any errors
}
Log.d("upload.onClick()", "Download URL for \"" + imageFilename + "\" is \"" + task.getResult() + "\".");
return task.getResult();
}
});
uploadedImageUrlTasks.add(currentUrlTask);
}
// At this point, all the files are being uploaded in parallel
// Each upload is tracked by the tasks in uploadedImageUrlTasks
Tasks.whenAllComplete(uploadedImageUrlTasks)
.addOnCompleteListener(new OnCompleteListener<List<Task<Uri>>>() {
#Override
public void onComplete(#NonNull List<Task<Uri>> tasks) {
int tasksCount = tasks.size();
List<Uri> failedUploads = new ArrayList<>();
imageAddressList.clear(); // empty old entries?
for (Task<Uri> task : tasks) {
if (task.isSuccessful()) {
successCount++;
Uri downloadUri = task.getResult();
imageAddressList.add(downloadUri.toString());
} else {
Uri imageUri = clonedImageList.get(tasks.indexOf(task));
failedUploads.add(imageUri);
Log.e("upload.onClick()", "Failed to upload/fetch URL for \"" + imageUri.getLastPathSegment() + "\" with exception", task.getException()); // log exception
}
}
progressDialog.dismiss(); // dismiss upload dialog
if (failedUploads.size() > 0) {
Toast.makeText(SignOutActivity.this, failedUploads.size() + "/" + tasksCount + " uploads failed.", Toast.LENGTH_LONG).show();
// TODO: Do something with list of failed uploads such as readd to the now empty upload list
imageList.addAll(failedUploads);
upload.setClickable(true);
} else {
Toast.makeText(SignOutActivity.this, "Successfully uploaded all " + tasksCount + " files.", Toast.LENGTH_LONG).show();
}
// TODO: Now that imageAddressList has been updated, update the UI - e.g tell recycler view to refresh
}
});
}
});

Related

Android Firebase - Remove uploaded image from Storage

I am developing a feature to remove uploaded files. I can currently remove the reference within Cloud Firestore but I am unable to remove it from Storage. The code bellow should remove from Storage but it does not work
private void deleteImage(String id, final int position, String fileUrl) {
StorageReference storageRef = FirebaseStorage.getInstance().getReference("uploads");
// Create a reference to the file to delete
StorageReference fileRef = storageRef.child(fileUrl);
// Delete the file
fileRef.delete().addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// File deleted successfully
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
// Uh-oh, an error occurred!
}
});
}
The error message is "No auth token for request. StorageException has occurred. Object does not exist at location. HttpResult: 404". I thought that the problem was related with my fileUrl field but I'm not sure. The fileUrl is the download image url, for example, if I copy and paste the url on web browser I can see the image. Why is not working?
As #blackapps commented, the download url that I was using was not suitable as reference to a file so I changed the value of fileUrl parameter to "filename.file_extension". The code still the same, i only changed the parameter value.

Upload images according to position no firebase

I'm creating a system for storing multiple image links in firestore, and it's been working, I select from the gallery and upload them as List it is in the order that it is going to the bank (firestore ), because it matters to me, I select for example the images 1,2,3, and when I save their link in the firestore it gets 2,1,3 or 3,1, 2 or 3,2,1 never in the order I loaded, it seems to be according to the lightest image, as saved according to the position I loaded? I've been breaking my head with this for a few days.
already to get each image from List.get(i) to save but to no avail.
I'm saving like this in firestore ->
list_img [
0 link_img
1 link_img
2 link_img]
firestore scheme
upload from gallery
GalleryConfig config = new GalleryConfig.Build()
.limitPickPhoto(50)
.singlePhoto(false)
.hintOfPick("this is pick hint")
.filterMimeTypes(new String[]{})
.build();
GalleryActivity.openActivity(Pag_producao_hq.this, reqCode, config);
upload
private void upload_Fotos_selecionadas(Uri uri, final int totalimg, int i) {
Log.i("sdsd77", String.valueOf(i));
final StorageReference ImageFolder = FirebaseStorage.getInstance().getReference().child("imagens");
String nomeImagem = UUID.randomUUID().toString();
final StorageReference imagename = ImageFolder
.child("HQ")
.child(identificadorUsuario)
.child(nomeImagem);
arrayListImageRef.add(ImageFolder); //arraylist of type StorageRef
arrayListImageRef.add(imagename);
imagename.putFile(uri)
.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
#Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
imagename.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
String urlConvertida = uri.toString();
lista_url.add(urlConvertida);
if(lista_url.size()==totalimg){
Map<String, Object> new_imagens = new HashMap<>();
new_imagens.put("list_img", lista_url);
db.collection("HQ")
.document(getId())
.collection("Imagens")
.add(new_imagens);
dialog.dismiss();
finish();
}
}
});
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
if (!isFinishing()) {
dialog.dismiss();
}
Toast.makeText(getApplicationContext(), exception.getMessage(), Toast.LENGTH_LONG).show();
}
});
}
Cloud Firestore does not list document as the same order of insertion. Auto IDs are not time related. You will need to add a timestamp field and query your data using order by this field.

Firebase getting url after image upload

I have an application where I want to upload two images , one is a normal image and the second is a thumbnail. I am ignoring the thumbnail for now and only focusing on the main image. The steps I am working on are the following:
Step 1 : Upload image
Step 2 : Get Download link as string
Step 3 : Add download link to real time database in firebase
I am stuck on step 2
I have done the following :
else if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
CropImage.ActivityResult result = CropImage.getActivityResult(data);
if (resultCode == RESULT_OK) {
{
Uri resultUri = result.getUri();
File thumb_filePath = new File(resultUri.getPath());
Bitmap thumb_bitmap = null;
try {
thumb_bitmap = new Compressor(this)
.setMaxWidth(200)
.setMaxHeight(200)
.setQuality(75)
.compressToBitmap(thumb_filePath);
} catch (IOException e) {
e.printStackTrace();
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
thumb_bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
final byte[] thumb_byte = baos.toByteArray();
final StorageReference mStorageThumbPathRef = mStorageRef.child("chatappthumbimg").child(current_userid + ".jpg");
final StorageReference mStoragePathRef = mStorageRef.child("chatappimg").child(current_userid + ".jpg");
UploadTask uploadTask;
uploadTask = mStoragePathRef.putFile(resultUri);
Task<Uri> urlTask = uploadTask.continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
#Override
public Task<Uri> then(#NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
if (!task.isSuccessful()) {
throw task.getException();
}
// Continue with the task to get the download URL
return mStoragePathRef.getDownloadUrl();
}
}).addOnCompleteListener(new OnCompleteListener<Uri>() {
#Override
public void onComplete(#NonNull Task<Uri> task) {
if (task.isSuccessful()) {
Uri downloadUri = task.getResult();
} else {
// Handle failures
// ...
}
}
});
}
}
}
And I used the documentation for help : https://firebase.google.com/docs/storage/android/upload-files
However, now I am unsure how to proceed.
Does mStoragePathRef.getDownloadUrl(); gives me back the real url of the image?
Because upon some earlier tests I got some kind of task and not an image url
Per comments above, OP requested to see the way I address uploads in my project - which is unfortunately not Android. I don't expect this will help much since this isn't the right language, but take whatever you can from it.
Specifically, this is done in Angular 6 using the AngularFire2 package. I included the complete function for reference, but the relevant portion is towards the end, talking about this.downloadURLObservable and this.downloadURLSubscription$
// Uploads file to Firebase storage, and returns the file's access URL
pushUpload(pageName, upload) {
// Returns a promise, so we can use .then() when pushUpload is called
return new Promise( (resolve, reject) => {
this.uploadPercent = 0;
// Include the current timeStamp in the file name, so each upload can be uniquely identified - no 1 photo will ever be used in 2 places, can safely delete this file later w/o fear of messing up something else
const timeStamp = new Date().getTime();
// Upload the file
const uploadTask = this.afStorage.upload(`${pageName}/${timeStamp}-${upload.file.name}`, upload.file);
// Observe percentage changes
this.uploadPercentObservable = uploadTask.percentageChanges();
this.uploadPercentageSubscription$ = this.uploadPercentObservable.subscribe(
eachValue => {
this.uploadPercent = Math.round(eachValue*10) / 10
},
err => {
console.log('uploadPercentageSubscription$ errored out in upload.service.ts, here is the err:')
console.log(err)
},
() => {
console.log('uploadPercentageSubscription$ completed')
}
)
// Get notified when the download URL is available, return it from the function
uploadTask.snapshotChanges().pipe( finalize( () => {
this.downloadURLObservable = this.afStorage.ref(`${pageName}/${timeStamp}-${upload.file.name}`).getDownloadURL()
this.downloadURLSubscription$ = this.downloadURLObservable.subscribe(
eachValue => {
resolve(eachValue)
},
err => {
console.log('downloadURLSubscription$ errored out in upload.service..ts, here is the err:')
console.log(err)
},
() => {
console.log('downloadURLSubscription$ completed')
}
)
})).subscribe()
}); // End of returned promise
} // End of pushUpload() for regular image
For anyone who might be stuck at the same thing , Uri downloadUri = task.getResult(); is the one that has the real download url

Firebase Storage getDownloadUrl() always return last result I select

I'm trying to play the mp3 files which are stored on Firebase storage by getting downloadUrl.
I use two spinners to decide the music types and music name which matches the folder name and file name on storage.
But after the first time choosing, the Uri I get is Null.
When I choose the second one the Uri I get is the first one I just choose.
I choose the third one, I get the second one and so on.
Here is the code I get the Url.
private void prepareMusic() {
btnPlay.setText(getString(R.string.btnplay));
btnPlay.setEnabled(false);
btnStop.setEnabled(false);
mStorageRef.child("music/"+musicType+"/"+musicName).getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
uriTest = uri.toString();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
//uriTest =" ";
}
});
//the toast here is the correct type and name I choose
Toast.makeText(this,"Now musicType is: " + musicType + " musicName is:" + musicName, Toast.LENGTH_SHORT).show();
//the Uri here is always last one I choose and null at beginning
Toast.makeText(this,"uri is: " + uriTest, Toast.LENGTH_LONG).show();
try{
mper.reset();
mper.setDataSource(uriTest);
mper.prepareAsync();
}catch (Exception e){
tos.setText(getString(R.string.setTrackError) + e.toString());
tos.show();
}
}
I have searched lots of question here, but there is not a good answer to deal with my problem.
getDownloadUrl() is asynchronous and returns immediately after it's called with a Task object that represents the work in progress. Your code will then display two toasts before the download url is available.
You should only be using the download URL from the moment that the success callback is invoked, so move your code in there, or call a method in there that uses the URL.
To learn more about why Firebase APIs are asynchronous, read this blog.

Add document is queried but not added on the console nor listener is called

Ok I am a running into a weird problem with firestore
I have the following structure
collection1 -> document1- >collection2
I am adding a new document to collection2 with on complete listener( I tried the success listener as well). No errors are shown. The new document is not shown on the console. The listener is not being called. However, when I query, I get ALL the added documents including the new ones. What's going on here?
Map<String, Object> data = new HashMap<>();
data.put("completed", true;
data.put("date_completed", new Date());
data.put("location", "123 main st");
data.put("notes", "");
data.put("work", "");
db.collection("collection1").document(document1).collection("collection2")
.add(data)
.addOnCompleteListener(new OnCompleteListener<DocumentReference>() {
#Override
public void onComplete(#NonNull Task<DocumentReference> task) {
if (task.isSuccessful()) {
DocumentReference documentReference = task.getResult();
Log.d(TAG, "DocumentSnapshot written with ID: " + documentReference.getId());
det.setDocId(documentReference.getId());
addr_arr.add(det);
} else {
Log.w(TAG, "Error adding document", task.getException());
Toast.makeText(EditListActivity.this, "Failed operation - " + task.getException().getMessage(),
Toast.LENGTH_SHORT).show();
hideProgressDialog();
}
}
});
Here is the query I do
CollectionReference collecRef = db.collection("collection1").document(document1).collection("collection2");
collecRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
// here I do document.get on all the fields, add them to object and add object to array
}
adapter.notifyDataSetChanged();
} else {
Log.d(TAG, "get failed with ", task.getException());
Toast.makeText(EditListActivity.this, "Failed operation - " + task.getException(),
Toast.LENGTH_SHORT).show();
hideProgressDialog();
}
}
});
Your app is acting like it's offline or somehow lacking a working internet connection.
When there is no network connection, the Firestore SDK won't fail on writes. It will store the write locally, and eventually sync that to the server. In the meantime, the local write becomes part of any queries, just like if the data was available on the server. So probably what you're seeing is the result of your local write.
(This is the same behavior you would see with Realtime Database.)
As far why the Firestore SDK doesn't have a connection, I don't know. You might have to troubleshoot that on your own.

Categories

Resources