Search code examples
androidfirebasefirebase-storageillegalstateexception

firebase storage java.lang.IllegalStateException: Task is not yet complete


I want to download my all files from the firebase folder on android.

this is my code

try {
        StorageReference reference = storage.getReference().child("Ringtone");
    reference.listAll().addOnCompleteListener(new OnCompleteListener<ListResult>() {
        @Override
        public void onComplete(@NonNull Task<ListResult> task) {
            if (task.isComplete()){
            task.addOnSuccessListener(new OnSuccessListener<ListResult>() {    /////line 82  == 1st line where i am getting error
                @Override
                public void onSuccess(ListResult listResult) {
                    for (StorageReference item : listResult.getItems()) {

                        RingtoneModel ringtoneModel = new RingtoneModel();
                        ringtoneModel.setUri(item.getDownloadUrl().getResult());      /////line 88 == 2nd line where i am getting  error here
                        item.getMetadata().addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
                            @Override
                            public void onSuccess(StorageMetadata storageMetadata) {
                                ringtoneModel.setSongName(storageMetadata.getName());
                                Toast.makeText(MainActivity.this, "ring name saved", Toast.LENGTH_SHORT).show();
                            }
                        }).addOnFailureListener(new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {
                                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                                Log.e("metadata error",e.getMessage());
                            }
                        });
                    }
                }
            });}else {
                Toast.makeText(MainActivity.this, (CharSequence) task.getException(), Toast.LENGTH_SHORT).show();
            }
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            Log.e("list all error",e.getMessage());
        }
    }); }catch (Exception e){
        Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
        Log.e("all errors",e.getMessage());}
}

Logcat error message

   /com.example.alarm E/AndroidRuntime: FATAL EXCEPTION: main
        Process: com.example.alarm, PID: 433
        java.lang.IllegalStateException: Task is not yet complete
            at com.google.android.gms.common.internal.Preconditions.checkState(Unknown Source)
            at com.google.android.gms.tasks.zzu.zzb(Unknown Source)
            at com.google.android.gms.tasks.zzu.getResult(Unknown Source)
            at com.example.alarm.MainActivity$3.onSuccess(MainActivity.java:88)
            at com.example.alarm.MainActivity$3.onSuccess(MainActivity.java:82)

my folders and files path in firebase database Firebase storage >Rington<my files

<--what i am tried --> i tried adding addonsuuccesslistner , addoncompletelitner, task.isComplete(), task.issuccessfull() and may more things


Solution

  • While addOnSuccessListener and addOnFailureListener are useful for handling the results of an asynchronous task, they aren't very useful when you need to chain things together like you are here.

    Looking at your code, you call the following line:

    StorageReference item;
    
    item.getDownloadUrl().getResult()
    

    The exception you get is thrown because you aren't waiting for this task to finish before calling getResult().

    Task<String> downloadUrlTask = item.getDownloadUrl();
    
    downloadUrlTask
      .addOnSuccessListener(result -> {
        // now do something with result
      })
      .addOnFailureListener(e -> {
        // now do something with the exception
      });
    

    While the above code is helpful if that's all you needed, what you are trying to do is wait for it to finish along with getMetadata() so you can create an instance of your RingtoneModel class.


    This is where the Tasks utility class comes in.

    Tasks.whenAll(Collection<Task<?>> tasks) will succeed only when all of the tasks in tasks successfully finish.

    Task<String> downloadUrlTask = item.getDownloadUrl();
    Task<StorageMetadata> metadataTask = item.getMetadata();
    
    return Tasks
        .whenAll([ // wait for these tasks to finish successfully
            downloadUrlTask,
            metadataTask
        ])
        .addOnSuccessListener(() -> { // then assemble its RingtoneModel
            RingtoneModel ringtoneModel = new RingtoneModel();
            ringtoneModel.setUri(downloadUrlTask.getResult());
            ringtoneModel.setSongName(metadataTask.getResult().getName());
    
            // we've now got a RingtoneModel! but what to do with it?
        });
    

    Instead of using addOnSuccessListener() in the above code, if we use onSuccessTask() we can return the RingtoneModel back so that the Task<void> from Tasks.whenAll() is now a Task<RingtoneModel>. To do this, we need to use Tasks.forResult() which will take our RingtoneModel and turn it into a Task<RingtoneModel>.

    // function that takes a StorageReference and
    // returns a task that resolves to a RingtoneModel
    StorageReference item -> {
        Task<String> downloadUrlTask = item.getDownloadUrl();
        Task<StorageMetadata> metadataTask = item.getMetadata();
    
        return Tasks
            .whenAll([ // wait for these tasks to finish successfully
                downloadUrlTask,
                metadataTask
            ])
            .onSuccessTask(() -> { // then assemble its RingtoneModel
                RingtoneModel ringtoneModel = new RingtoneModel();
    
                // because these task have finished by now,
                // you can safely use getResult() here
                ringtoneModel.setUri(downloadUrlTask.getResult());
                ringtoneModel.setSongName(metadataTask.getResult().getName());
    
                return Tasks.forResult(ringtoneModel);
            });
    }
    

    Now that we have a RingtoneModel for one StorageReference, we want to turn the List<StorageReference> into a List<RingtoneModel>. We can do this using

    List<Task<RingtoneModel>> getRingtoneTasks = listResult.getItems()
        .stream()
        .map(convertToRingtoneModel); // the function above
    

    Note here that we have List<Task<RingtoneModel>> instead of Task<List<RingtoneModel>>. To flip this around, we make use of Tasks.whenAll(), onSuccessTask(), stream().map() and Tasks.forResult() again.

    return Tasks
        .whenAll(getRingtoneTasks)
        .onSuccessTask(() -> {
            return Tasks.forResult(
                getRingtoneTasks
                    .stream()
                    .map(task -> task.getResult());
            );
        });
    

    Now you might look at that block above and think that's a pretty neat method to have as a utility method, and you are right, because you can just use this to do the same thing:

    return Tasks.whenAllSuccess(getRingtoneTasks);
    

    Mashing all of this together, gives:

    public Task<List<RingtoneModel>> getRingtones() {
        StorageReference reference = storage.getReference().child("Ringtone");
        return reference.listAll() // find all objects under /Ringtone
            .onSuccessTask(listResult -> {
                // for each item found, assemble a RingtoneModel
                List<Task<RingtoneModel>> getRingtoneTasks = listResult.getItems()
                    .stream()
                    .map((item) -> {
                        // A RingtoneModel needs the download URL and the object's metadata
                        Task<String> downloadUrlTask = item.getDownloadUrl();
                        Task<StorageMetadata> metadataTask = item.getMetadata();
    
                        return Tasks
                            .whenAll([ // wait for these tasks to finish successfully
                                downloadUrlTask,
                                metadataTask
                            ])
                            .onSuccessTask(() -> { // then assemble the RingtoneModel and return it
                                RingtoneModel ringtoneModel = new RingtoneModel();
                                ringtoneModel.setUri(downloadUrlTask.getResult());
                                ringtoneModel.setSongName(metadataTask.getResult().getName());
                                return Tasks.forResult(ringtoneModel);
                            });
                    });
    
                return Tasks.whenAllSuccess(getRingtoneTasks);
            });
    }
    

    Then when you want to get all the ringtones in your Cloud Storage you just need:

    getRingtones()
        .addOnSuccessListener(ringtoneModelList -> {
            Toast.makeText(MainActivity.this, "Found " + ringtoneModelList.size() + " ringtones.", Toast.LENGTH_SHORT).show();
            // I recommend storing ringtoneModelList in your database of choice
        })
        .addOnFailureListener(e -> {
            Toast.makeText(MainActivity.this, "Failed to get any ringtones.", Toast.LENGTH_SHORT).show();
            Log.e("getRingtones error", e); // <-- log the full error to help with debugging
        });