2

I have a database in Cloud Firestore where each document has a particular key "last update" with value, a String, representing a date in the form YYYY-MM-DD. Each time a document is updated, the value of "last update" is set as the date of the update.

Now, I want my activity to have a method that checks documents for their last update. As the documents contain fairly big lists of objects, this update check takes a few seconds. So I decided to defer it to an AsyncTask. The doInBackground method of the AsyncTask should create a DocumentReference, noteRef, for the document and read its "last update" with noteRef.get(), equipped with onSuccess- and onFailure listeners, into a String, which is then returned by the method.

In order to test this, I have created a toy activity, MyTestActivity, which calls the above AsyncTask with String arguments "myCollection" and "myDocument" and displays the value of this document's last update in a TextView. Now, instead of showing the actual value, "2019-10-03", the TextView displays the value, "1970-01-01", which is the one used in doInBackground to initialize the String variable which is returned. It's as if doInBackground doesn't bother to wait until the document has been read. The code is as follows.

public class MyTestActivity extends AppCompatActivity{ 


    private Button button;
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_test);

        button = findViewById(R.id.update_button);
        textView = findViewById(R.id.update_text_view);


    }


    public void buttonClicked(View view) throws ExecutionException, InterruptedException {

        UpdateTask task = new UpdateTask(this, "myCollection", "myDocument");
        String date =  task.execute().get();
        textView.setText("Last update on "+date);

    }


    private static class UpdateTask extends AsyncTask<Integer, Integer, String> {
        private WeakReference<MyTestActivity> activityWeakReference;
        String collection;
        String document;
        String lastUpdate;

        UpdateTask(MyTestActivity activity, String collection, String document) {
            activityWeakReference = new WeakReference<MyTestActivity>(activity);
            this.collection = collection;
            this.document = document;
            lastUpdate = new String();
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            MyTestActivity activity = activityWeakReference.get();
            if (activity == null || activity.isFinishing()) {
                return;
            }

        }

        @Override
        protected String doInBackground(Integer... params) {
            FirebaseFirestore db = FirebaseFirestore.getInstance();
            DocumentReference noteRef = db.collection(collection).document(document);
            lastUpdate = "1970-01-01";


            noteRef.get()
                    .addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
                        @Override
                        public void onSuccess(DocumentSnapshot documentSnapshot) {
                            if (documentSnapshot.exists()) {
                                Map<String, Object> map = documentSnapshot.getData();
                                lastUpdate = (String)map.get("last update");
                                activityWeakReference.get().textView.setText(lastUpdate);

                            } else {
                                lastUpdate = "Document doesn't exist";


                            }
                        }
                    })
                    .addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            lastUpdate = "Listener failed.";

                        }
                    });



            return lastUpdate;


        }
    }
}

Can anyone explain what is going on here?

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
Hank
  • 23
  • 4
  • 1
    You don't need an AsyncTask at all to work with Firestore. The Firbase APIs are all asynchronous and never block the main thread. An AsyncTask only complicates the code and adds nothing helpful. (In fact, in modern Android development, no one should be using AsyncTask, as there are much better options for async and multithreaded programming.) – Doug Stevenson Oct 04 '19 at 15:03

2 Answers2

2

I have a database in Firebase Firestore where each document has a particular key "last update" with value, a String, representing a date in the form YYYY-MM-DD.

That's uncommon to store the date as a String, instead you should store it as a:

FieldValue.serverTimestamp()

As explained in my answer from the following post:

So I decided to defer it to an AsyncTask.

The Cloud Firestore database client, already runs all network operations in a background thread. This means that all operations take place without blocking your main thread. Adding it in an AsyncTask does not give any any benefits at all.

Now, instead of showing the actual value, "2019-10-03", the TextView displays the value, "1970-01-01", which is the one used in doInBackground

This is happening because you are trying to return a message synchronously from a method that is asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended.

A quick solve for this problem would be to use value of lastUpdate only inside the onSuccess() method, otherwise I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • He's using Firebase Firestore not realtime database. But you're still right. – bashizip Oct 04 '19 at 08:59
  • @kylexy1357 Yes, you're right but what in my answer is referring to realtime database? – Alex Mamo Oct 04 '19 at 09:02
  • @Alex Mamo: I read the post you linked to and watched the video. Your suggested solution with custom callbacks does the job - almost. Indeed, Android Studio won't accept the argument myCallback inside the method readData, unless it is passed as a final MyCallback. I can't understand how it works for you in the video. Anyway, thanks a lot for your help! – Hank Oct 04 '19 at 12:58
  • You're very welcome and good to hear that the custom callbacks does the job. The idea behind this tehnique is that once you get the data from the database, you add it to the custom callback, moment in which that method is triggered. – Alex Mamo Oct 04 '19 at 13:04
  • @Alex it's confusing when you say " The Firebase realtime database". – bashizip Oct 05 '19 at 13:47
  • @Alex: Suppose I'd like to use the value retrieved from the document for further work. Now, if I just need it for a couple of methods, I could let each of these methods override myCallback.onCallback(). But if I have a huge number of methods, such an approach seems a little cumbersome as the data is retrieved from Firestore multiple times. I would prefer to just retrieve the data once and then be able to pass it as an argument in the various methods, like assigning an instance variable this value (which, however, does not work). Any idea of how to achieve this? – Hank Oct 24 '19 at 12:36
  • No, that's not how asynchronously APIs work. It takes time to get the data, so that why you need to use a custom callback or why not, a LiveData. – Alex Mamo Oct 24 '19 at 12:52
  • So, every method that I want to call with data from Firestore as a parameter needs to implement onCallback() for a new MyCallback? Is there no way of avoiding calling Firestore multiple times unless the documents have been updated? – Hank Oct 24 '19 at 13:59
  • This is how it actually works. The listeners are invoked only when a change in the database takes place. – Alex Mamo Oct 24 '19 at 14:00
0

Ouch! That's bad design. Firestore calls are asynchronous, so you don't need to put them into asyncTask background method. Also, using an synctask wont execute your code faster. What you need is a "loading message" until your OnSuccessListener fires back.

bashizip
  • 562
  • 5
  • 14