Search code examples
androidrx-androidandroid-architecture-componentsandroid-livedata

How to trigger LiveData SwitchMap even if there is no observer attached


Issue:

The switchMap transformation does not trigger until its result must have an active observer. I am trying to trigger a switchMap transformation even if there is no observer. Can anyone suggest how i can achieve this functionality? There is some code below with depicting the current scenario.

How to reproduce:

In ProfileViewModel, The UI can observe two different LiveData

  • profileLiveData : to observe Profile Data
  • profileApiStateLiveData : to observe the state of Http Call that fetches the profile data from backend

ProfileViewModel.java

public class ProfileViewModel extends AndroidViewModel {

    private MutableLiveData<Boolean> getProfileCommand = new MutableLiveData<>();

    public ProfileViewModel(@NonNull Application application) {
        super(application);
        profileLiveData = Transformations.switchMap(getProfileCommand, forceUpdate -> UserRepository.getInstance().getProfile(forceUpdate));
        profileApiStateLiveData = UserRepository.getInstance().getProfileApiRequestStatus();
    }

    public void loadProfile(boolean forceUpdate) {
        getProfileCommand.postValue(forceUpdate);
    }

    // Profile Data : Observable Field
    private LiveData<Profile> profileLiveData;
    public LiveData<Profile> getProfileLiveData() {
        if(profileLiveData == null) profileLiveData = new MutableLiveData<>();
        return profileLiveData;
    }

    // Profile Http Call's State : Observable Field
    private LiveData<ApiRequest>  profileApiStateLiveData;
    public LiveData<ApiRequest>  getProfileApiStateLiveData() {
        if(profileApiStateLiveData == null) profileApiStateLiveData = new MutableLiveData<>();
        return profileApiStateLiveData;
    }
}

Now the UI can observe changes in both Profile Data and State of Http Call. So if a UI want to download and display the Profile on UI, the UI is responsible to observe the profileLiveData and profileApiStateLiveData and then call the ViewModel's method loadProfile(true);

// Observes Profile Data
mProfileViewModel.getProfileLiveData().observe(this, profileData -> {
    // Use profile data here
});

// Observes State of Profile Http Call
mProfileViewModel.getProfileApiStateLiveData().observe(this, profileHttpCallState -> {
    // show/hide progress based on http call state
});

// start downloading profile data
mProfileViewModel.loadProfile(true);

We can see here that the method loadProfile will trigger the switchMap Transformation and will start the Http Call. Please note that switchMap trigger happens because the UI is observing the result LiveData which is profileLiveData.

This scenario works fine. But if a certain Activity only wants to initiate the Http Call and only want to observe the profileApiStateLiveData not the profileLiveData, then the switchMap Trigger never occur, this is because there is no active observer of result LiveData profileLiveData.

    /**** Do not observe Profile Data, because here we only need to observe Http Call State  ***/

    /**** Only Observe State of Profile Http Call  ***/
    mProfileViewModel.getProfileApiStateLiveData().observe(this, profileHttpCallState -> {
        // show/hide progress based on http call state
    });

    /**** start downloading profile data ***/
    mProfileViewModel.loadProfile(true);

    /**** The above line does not trigger the switchMap Transformation. ***/

There is an ugly solution i have in which i have to add an un-necessary blank observer for profileLiveData in the UI. But that is error-prone because the other developers can forget to add this un-necessary blank observer for profileLiveData and they would have no clue about why profile data is not being fetched even they are calling the method loadProfile(true).

The help from Rx experts is much appreciated here :-)


Solution

  • This question has been answered very well and thanks to @arka-prava-basu . The profileLiveData has been detached from Transformation and it is now being fetched directly from Room Database. And the action

    (UserRepository.getInstance().getProfile(forceUpdate) )

    that was being performed by this transformation has been now transferred to loadProfile method. Below is the refactored code.

    ProfileViewModel.java

    public class ProfileViewModel extends AndroidViewModel {
    
       public ProfileViewModel(@NonNull Application application) {
            super(application);
            profileLiveData = UserRepository.getInstance().getMemberInfo();
            profileApiStateLiveData = UserRepository.getInstance().getProfileApiRequestStatus();
        }
    
        public void loadProfile(boolean forceDownload) {
            if(forceDownload) {
                UserRepository.getInstance().initiateProfileDownloading();
            }
        }
    
        // Profile Data : Observable Field
        private LiveData<Profile> profileLiveData;
        public LiveData<Profile> getProfileLiveData() {
            return profileLiveData;
        }
    
        // Profile Http Call's State : Observable Field
        private LiveData<ApiRequest>  profileApiStateLiveData;
        public LiveData<ApiRequest>  getProfileApiStateLiveData() {
            return profileApiStateLiveData;
        }
    }