1

As I'm going through Firebase Storage and Database tutorials for Android, I came across to a problem to prioritize the queries.

I need to save a visitor data into Firestore Database and an image of a visitor in the Firebase Storage with a single button click. So, I have the following java method in Android:

public void saveNewVisitor() {
    Uri file = Uri.fromFile(new File(image_path));
    StorageReference imageRef = storageRef.child("images/"+file.getLastPathSegment());
    UploadTask uploadTask = imageRef.putFile(file);

    uploadTask.addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
            // Handle unsuccessful uploads
        }
    }).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
        @Override
        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
            downloadURL = taskSnapshot.getDownloadUrl();
        }
    });
//-----------------------------------
    Map<String, Object> dataToSave = new HashMap<String, Object>();
    dataToSave.put(NAME, visitorName);
    dataToSave.put(AGE, visitorAge);
    dataToSave.put(GENDER, visitorGender);
    dataToSave.put(IMAGE_URL, String.valueOf(downloadURL));

    CollectionReference mColRef = FirebaseFirestore.getInstance().collection("visitors");
    mColRef.add(dataToSave).addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
        @Override
        public void onSuccess(DocumentReference documentReference) {
            Log.d(TAG, "Visitor has been saved!");
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            Log.w(TAG, "Error adding a visitor", e);
        }
    });
}

In the above code, there are two actions happening. In the first part, an image is being saved in Firebase Storage and in OnSuccessListener it's generating URL to download that image for the future use. In the second part, the text data, including above generated URL is being saved in the Database.

So, the problem is, Android and Firebase is executing text data query first, then executing image saving query. This is resulting in URL = null in database as at the time of saving the data downloadURL = null.

My question is how to prioritize image saving query as HIGH to make sure it'll be executed before text data saving query. Firebase documents aren't that clear on this topic. Any kind of help or suggestion to resolve the problem is appreciated.

Dan
  • 85
  • 10

1 Answers1

0

Uploading a file to Firebase using an UploadTask is performed asynchronously and therefore your upload task is still running and has not yet completed before you call mColRef.add(dataToSave), which is also executed asynchronously.

In order to chain both of these asynchronous methods, you'll need to move the second half of your method logic inside the onSuccess() block of your UploadTask. That way, your database operation is only performed once your upload task has completed, and you are guaranteed to have access to the download URL.

For a complete example, you could split the method into two parts so it's easier to see what's happening:

public void saveNewVisitor() {
    Uri file = Uri.fromFile(new File(image_path));
    StorageReference imageRef = storageRef.child("images/"+file.getLastPathSegment());
    UploadTask uploadTask = imageRef.putFile(file);

    uploadTask.addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
            // Handle unsuccessful uploads
        }
    }).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
        @Override
        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
            downloadURL = taskSnapshot.getDownloadUrl();
            saveNewVisitorToDatabase(String.valueOf(downloadURL));
        }
    });
}

private void saveNewVisitorToDatabase(String downloadURL) {
    Map<String, Object> dataToSave = new HashMap<String, Object>();
    dataToSave.put(NAME, visitorName);
    dataToSave.put(AGE, visitorAge);
    dataToSave.put(GENDER, visitorGender);
    dataToSave.put(IMAGE_URL, downloadURL);

    CollectionReference mColRef = FirebaseFirestore.getInstance().collection("visitors");
    mColRef.add(dataToSave).addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
        @Override
        public void onSuccess(DocumentReference documentReference) {
            Log.d(TAG, "Visitor has been saved!");
            // Once we reach here, we can guarantee that the upload task
            // and this database operation were both successful
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            Log.w(TAG, "Error adding a visitor", e);
        }
    });
}
Grimthorr
  • 6,856
  • 5
  • 41
  • 53
  • This works well, but what if an image is not available for given entry? No image needs to be saved, but there is still text data needs to be saved. In this scenario, `onSuccess` never be triggered and neither method `saveNewVisitorToDatabase`. Thanks. – Dan Oct 23 '17 at 17:42
  • Full Disclosure: - I have several images per a single text data and there is no guarantee how many images, if any, are available per text data. Currently it's running with lots of `if-statements` and it doesn't look nice both by readability and execution of the code. – Dan Oct 23 '17 at 18:08
  • This is another reason for splitting the two methods - you can just call `saveNewVisitorToDatabase()` instead when there are no images to save. – Grimthorr Oct 24 '17 at 08:57