Does whereEqualTo/snapshot listener run on a background thread?

Viewed 171

I am creating a journal app. I am trying to fetch the current users journal entries and then convert that into a journal object which will be put into a journal list. Eventually, this journal list will be sent to the RecyclerView Adapter.

I have this piece of code in onCreate():

myCollection.whereEqualTo("userId", userId).addSnapshotListener(new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(@Nullable @org.jetbrains.annotations.Nullable QuerySnapshot value, @Nullable @org.jetbrains.annotations.Nullable FirebaseFirestoreException error) {
                for(QueryDocumentSnapshot snapshot : value){
                    Journal myJournal = snapshot.toObject(Journal.class);
                    journals.add(myJournal);

                     
                    RecyclerViewAdapter myAdapter = new RecyclerViewAdapter(journals, JournalList.this);
                    recyclerView.setAdapter(myAdapter);
                    recyclerView.setLayoutManager(new LinearLayoutManager(JournalList.this));
                    myAdapter.notifyDataSetChanged();

                }
            }
        });

If I move this part:

RecyclerViewAdapter myAdapter = new RecyclerViewAdapter(journals, JournalList.this);
                        recyclerView.setAdapter(myAdapter);
                        recyclerView.setLayoutManager(new LinearLayoutManager(JournalList.this));
                        myAdapter.notifyDataSetChanged();

out of the onEvent block(but still in onCreate() ), the journal is still sent to firebase, but it seems like the RecyclerViewAdapter isn't invoked until I add the second post.

My guess is that either Android Studio skips over the onEvent() block and continues on(possibly puts it in a queue considering it knows it will take time to execute), or it runs on a background thread in which the adapter part finishes first. Either way, an empty arrayList of journals is sent to Firestore.

However, I'm unsure which one of these scenarios is actually occurring. If someone could confirm, I would appreciate it. Thanks.

Update: Code that doesn't work:

myCollection.whereEqualTo("userId", userId).addSnapshotListener(new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(@Nullable @org.jetbrains.annotations.Nullable QuerySnapshot value, @Nullable @org.jetbrains.annotations.Nullable FirebaseFirestoreException error) {
                for(QueryDocumentSnapshot snapshot : value){
                    Journal myJournal = snapshot.toObject(Journal.class);
                    journals.add(myJournal);



                }
            }
        });

        
        RecyclerViewAdapter myAdapter = new RecyclerViewAdapter(journals, JournalList.this);
        recyclerView.setAdapter(myAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(JournalList.this));
        myAdapter.notifyDataSetChanged();

Full code of JournalList.java(if needed):

import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.google.firebase.firestore.QuerySnapshot;

import java.util.ArrayList;
import java.util.List;

public class JournalList extends AppCompatActivity {

    private RecyclerView recyclerView;
    private FirebaseFirestore db = FirebaseFirestore.getInstance();
    private CollectionReference myCollection = db.collection("Journal");
    private FirebaseAuth firebaseAuth;
    private FirebaseUser currentUser;
    private List<Journal> journals;
    private Toolbar myToolbar;

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

        recyclerView = findViewById(R.id.recyclerView);

        firebaseAuth = FirebaseAuth.getInstance();
        currentUser = firebaseAuth.getCurrentUser();
        String userId = currentUser.getUid();
        myToolbar = findViewById(R.id.my_toolbar);
        journals = new ArrayList<>();

        setSupportActionBar(myToolbar);




        myCollection.whereEqualTo("userId", userId).addSnapshotListener(new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(@Nullable @org.jetbrains.annotations.Nullable QuerySnapshot value, @Nullable @org.jetbrains.annotations.Nullable FirebaseFirestoreException error) {
                for(QueryDocumentSnapshot snapshot : value){
                    Journal myJournal = snapshot.toObject(Journal.class);
                    journals.add(myJournal);



                }
            }
        });

        //my guess:
        RecyclerViewAdapter myAdapter = new RecyclerViewAdapter(journals, JournalList.this);
        recyclerView.setAdapter(myAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(JournalList.this));
        myAdapter.notifyDataSetChanged();










    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        getMenuInflater().inflate(R.menu.menu, menu);

        return super.onCreateOptionsMenu(menu);



    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()){
            case R.id.addNote:

                startActivity(new Intent(JournalList.this, PostJournalActivity.class));

                break;
            case R.id.sign_out:

                firebaseAuth.signOut();
                startActivity(new Intent(JournalList.this, MainActivity.class));

                break;
        }

        return super.onOptionsItemSelected(item);


    }

    @Override
    protected void onDestroy() {

        super.onDestroy();
    }
}
2 Answers

The network request etc runs on a background thread, but the callback doesn't.

However I think your misunderstanding here comes from how callbacks work. When you pass in an EventListener here, you're passing in a class (in this case, an anonymous class) which overrides the onEvent method. There's nothing to "skip over" as you mentioned.

Let's consider an example. Say I defined an interface Callback:

interface Callback {

    void myMethod();
}

Then I can write a redundant method:

void doSomethingWith(Callback myCallback) {

    myCallback.myMethod();
}

Now here, when I call

doSomethingWith(new Callback() {
    @Override
    public void myMethod() {
        
        // ...
    }
});

doSomethingElse();

the implementation of myMethod runs before doSomethingElse.

However, it doesn't have to be this way. Say I instead defined doSomethingWith like this:

void doSomethingWith(Callback myCallback) {

    // Do nothing
}

Then the code in your implementation of myMethod will never get called. Just because you passed in a class that implemented a method, doesn't guarantee when it will get called or whether it will get called at all.

To bring this back to Firebase a bit, we can consider an example like this:

void doSomethingWith(Callback myCallback) {

    // Switch to a background thread,
    // wait for a network request then call myCallback.myMethod()
}

So here your callback will get called, but it will be at a later time, when a network request has completed, exactly the same scenario as with your Firebase listener.


Now let's actually address solving your problem. It would make the most sense (to me) to declare the adapter first, then when onEvent is called, update the data:

RecyclerViewAdapter myAdapter = new RecyclerViewAdapter(journals, JournalList.this);
recyclerView.setAdapter(myAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(JournalList.this));

myCollection...addSnapshotListener(new EventListener<QuerySnapshot>() {
    @Override
    public void onEvent(...) {

        myAdapter.data = ...
        myAdapter.notifyDataSetChanged();
    }
});

As a side note, Android Studio definitely doesn't play a part here, it's an IDE. Don't get Android confused with Android Studio.

Related