Passing query parameters to LiveData/Viewmodel in Android app

379 views Asked by At

Since I'm new to programming Android apps I followed a tutorial on how to use the Android Architecture Components and Firebase for implementing the MVVM (using LiveData, ViewModel, etc.).

The tutorial I followed can be found here:

I'm now left with what I think is a decent implementation of the MVVM, but I can not wrap my head around how I am supposed to pass query parameters to it. Right now I need to hardcode the ID of the document I want to retrieve:

public class AlarmDAO {

    private FirebaseFirestore firebaseFirestore = FirebaseFirestore.getInstance();

    public AlarmLiveData getFirestoreLiveData() {
        DocumentReference documentReference = firebaseFirestore.collection(Collection.ALARMS.name).document("5RxJNuNyhDJlz49wpBkw");

        return new AlarmLiveData(documentReference);
    }

}

That then gets called by a class extending ViewModel.

public class AlarmViewModel extends ViewModel {

    private AlarmDAO DAO = new AlarmDAO();
    private AlarmLiveData liveData = null;

    public LiveData<Alarm> getAlarmLiveData() {
        liveData = DAO.getFirestoreLiveData();
        return liveData;
    }

    public LiveData<Alarm> getAlarm() {
        return liveData.alarm;
    }

}

And then I observe that data in my activity:

model.getAlarmLiveData().observe(this, Observable -> {});
model.getAlarm().observe(this, alarm -> {
   if (alarm != null) {
      alarmTextView.setText(alarm.getTest());
   else {
      Log.d(TAG, "Waiting for data");
   }
});

My problem is that I do not see a way of querying for a specific alarm. For instance model.getAlarm("someId"). I am under the impression that it should be done in the DAO and/or the ViewModel, but I can't figure out how. Another thing I do not understand is why I need to observe both model.getAlarmLiveData() and model.getAlarm() in my activity, as using only one does not work. The answer to both of those questions is most likely very simple, but thus far I haven't been able to figure it out.

For completeness: the Alarm class is nothing besides a getter and setter for two strings, and the AlarmLiveData class is below.

public class AlarmLiveData extends LiveData<Alarm> implements EventListener<DocumentSnapshot> {

    private static final String TAG = AlarmLiveData.class.getSimpleName();

    private Alarm alarmTemp = new Alarm();
    private DocumentReference documentReference;
    private ListenerRegistration listenerRegistration = () -> {};

    public MutableLiveData<Alarm> alarm = new MutableLiveData<>();

    public AlarmLiveData(DocumentReference documentReference) {
        this.documentReference = documentReference;
    }

    @Override
    protected void onActive() {
        listenerRegistration = documentReference.addSnapshotListener(this);
        super.onActive();
    }

    @Override
    protected void onInactive() {
        listenerRegistration.remove();
        super.onInactive();
    }


    @Override
    public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) {
        if (documentSnapshot != null && documentSnapshot.exists()) {
            alarmTemp = new Alarm();
            alarmTemp.setId(documentSnapshot.getId());
            alarmTemp.setTest(documentSnapshot.get("test").toString());
            alarm.setValue(alarmTemp);
        } else {
            Log.d(TAG, "ERROR");
        }
    }
}

Thank you for reading, I'm looking forward to the answer(s)!

1

There are 1 answers

0
Sammy T On

The reason you have to use both model.getAlarmLiveData() and model.getAlarm() looks to be that your AlarmLiveData class extends LiveData but sets a value for the contained MutableLiveData member variable instead of setting its own class value.

Inside your AlarmLiveData class:

// Comment out/Remove your 'public MutableLiveData<alarm> alarm' member variable from the top.
// You're going to want to set the value of the AlarmLiveData class itself instead.

// ...

// Then inside of your onEvent callback
@Override
public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) {
    if (documentSnapshot != null && documentSnapshot.exists()) {
        alarmTemp = new Alarm();
        alarmTemp.setId(documentSnapshot.getId());
        alarmTemp.setTest(documentSnapshot.get("test").toString());
        
        // Set the value for the AlarmLiveData class directly
        setValue(alarmTemp);
    } else {
        Log.d(TAG, "ERROR");
    }
}

I'm not sure why you're creating a DAO class and I would most likely move that code directly into the AlarmViewModel class. But, here is how you can alter your current DAO class if you don't want to remove it:

// Pass in the document id you want to create a document reference for
public AlarmLiveData getFirestoreLiveData(String documentId) {
    DocumentReference documentReference = firebaseFirestore.collection(Collection.ALARMS.name).document(documentId);

    return new AlarmLiveData(documentReference);
}

Your AlarmViewModel class would look something like this:

public class AlarmViewModel extends ViewModel {

    private AlarmDAO DAO = new AlarmDAO();
    private AlarmLiveData liveData = null;

    // Make sure to take in the document id so you can create the corresponding LiveData
    public LiveData<Alarm> getAlarmLiveData(String documentId) {
        // Only create a new LiveData instance if the current one is null.
        // This is helpful if you intend to use this as a Shared ViewModel.
        if(liveData == null){
            liveData = DAO.getFirestoreLiveData(documentId);
        }
        
        return liveData;
    }
}

Finally, in your Activity:

// Pass in the document id and observe the ViewModel
model.getAlarmLiveData("MY_DOCUMENT_ID").observe(this, alarm -> {
   if (alarm != null) {
      alarmTextView.setText(alarm.getTest());
   }else{
      Log.d(TAG, "Waiting for data");
   }
});