AndroidViewModel - making duplicate calls does not return data in the watch function

My question related to ViewModel returns null a second time in which I don't get a callback in observe if I make a callback to the server. Below is the code I'm using -

 @Singleton public class NetworkInformationViewModel extends AndroidViewModel { private LiveData<Resource<NetworkInformation>> networkInfoObservable; private final APIClient apiClient; @Inject NetworkInformationViewModel(@NonNull APIClient apiClient, @NonNull Application application) { super(application); this.apiClient = apiClient; getNetworkInformation(); } public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() { return networkInfoObservable; } // making API calls and adding it to Observable public void getNetworkInformation() { networkInfoObservable = apiClient.getNetworkInformation(); } } 

In an action, a ViewModel is defined as specified -

 final NetworkInformationViewModel networkInformationViewModel = ViewModelProviders.of(this, viewModelFactory).get(NetworkInformationViewModel.class); observeViewModel(networkInformationViewModel); 

The observeViewModel function observeViewModel used to add an observable to the ViewModel .

 public void observeViewModel(final NetworkInformationViewModel networkInformationViewModel) { networkInformationViewModel.getNetworkInfoObservable() .observe(this, networkInformationResource -> { if (networkInformationResource != null) { if (networkInformationResource.status == APIClientStatus.Status.SUCCESS) { Timber.d("Got network information data"); } else { final Throwable throwable = networkInformationResource.throwable; if (throwable instanceof SocketTimeoutException) { final NetworkInformation networkInformation = networkInformationResource.data; String error = null; if (networkInformation != null) { error = TextUtils.isEmpty(networkInformation.error) ? networkInformation.reply : networkInformation.error; } Timber.e("Timeout error occurred %s %s", networkInformationResource.message, error); } else { Timber.e("Error occurred %s", networkInformationResource.message); } if (count != 4) { networkInformationViewModel.getNetworkInformation(); count++; // Uncommenting following line enables callback to be received every time //observeViewModel(networkInformationViewModel); } } } }); } 

Uncommenting the next line in the above function allows the callback to arrive every time, but there must be a proper way to do this.

 //observeViewModel(networkInformationViewModel); 

Please note: - I do not need an RxJava implementation to implement this.

+5
source share
3 answers

Now in getNetworkInformation() you are:

  • Create a new LiveData
  • Updating LiveData with setValue

Instead, you should have one LiveData for the APIClient created as a member variable, and then in getNetworkInformation () just update that LiveData member.

In general, your APIClient is a data source. For data sources, you can contain member elements of a LiveData object that are updated when data changes. You can provide recipients with those LiveData objects to make them available in ViewModels, and ultimately listen to them in your actions / snippets. This is similar to how you can take another data source, such as Room, and listen to the LiveData returned by Room.

So, the code in this case will look like this:

 @Singleton public class APIClient { private final MutableLiveData<Resource<NetworkInformation>> mNetworkData = new MutableLiveData<>(); // Note this needs to be MutableLiveData so that you can call setValue // This is basically the same code as the original getNetworkInformation, instead this returns nothing and just updates the LiveData public void fetchNetworkInformation() { apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() { @Override public void onResponse( @NonNull Call<NetworkInformation> call, @NonNull Response<NetworkInformation> response ) { if (response.body() != null && response.isSuccessful()) { mNetworkData.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null)); } else { mNetworkData.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message())); } } @Override public void onFailure(@NonNull Call<NetworkInformation> call, @NonNull Throwable throwable) { mNetworkData.setValue( new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable)); } }); } // Use a getter method so that you can return immutable LiveData since nothing outside of this class will change the value in mNetworkData public LiveData<Resource<NetworkInformation>> getNetworkData(){ return mNetworkData; } } 

Then in your ViewModel ...

 // I don't think this should be a Singleton; ViewModelProviders will keep more than one from being instantiate for the same Activity/Fragment lifecycle public class SplashScreenViewModel extends AndroidViewModel { private LiveData<Resource<NetworkInformation>> networkInformationLiveData; @Inject SplashScreenViewModel(@NonNull APIClient apiClient, @NonNull Application application) { super(application); this.apiClient = apiClient; // Initializing the observable with empty data networkInfoObservable = apiClient.getNetworkData() } public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() { return networkInformationLiveData; } } 

Your activity may be the same as you originally encoded it; it will just receive and see LiveData from the ViewModel.

So what is Transformations.switchMap for?

switchMap is not required here because you do not need to change the underlying instance of LiveData in APIClient . This is because in fact there is only one piece of changing data. Say, instead, your APIClient for some reason needed 4 different LiveData, and you wanted to change which LiveData you watched:

 public class APIClient { private MutableLiveData<Resource<NetworkInformation>> mNetData1, mNetData2, mNetData3, mNetData4; ... } 

Then let's say that your fetchNetworkInformation will reference different LiveData to observe depending on the situation. It might look like this:

 public LiveData<Resource<NetworkInformation>> getNetworkInformation(int keyRepresentingWhichLiveDataToObserve) { LiveData<Resource<NetworkInformation>> currentLiveData = null; switch (keyRepresentingWhichLiveDataToObserve) { case 1: currentLiveData = mNetData1; break; case 2: currentLiveData = mNetData2; break; //.. so on } // Code that actually changes the LiveData value if needed here return currentLiveData; } 

In this case, the actual LiveData coming from getNetworkInformation is a change, and you also use some kind of parameter to determine which LiveData you want. In this case, you should use switchMap because you want to make sure that the watch statement that you called in your Activity / Fragment is watching the LiveData returned from your APIClient , even if you change the underlying instance of LiveData. And you do not want to call observation again.

Now this is a slightly abstract example, but basically this is what your Room Dao calls do - if you have a Dao that queries your RoomDatabase based on an identifier and returns a LiveData , it will return another instance of LiveData based on the identifier.

+1
source

I have already updated the answer related question . Re-posting here since I put generosity to the question and hopefully someone will verify that this is the right way to handle this problem.

Below is an updated working solution -

 @Singleton public class SplashScreenViewModel extends AndroidViewModel { private final APIClient apiClient; // This is the observable which listens for the changes // Using 'Void' since the get method doesn't need any parameters. If you need to pass any String, or class // you can add that here private MutableLiveData<Void> networkInfoObservable; // This LiveData contains the information required to populate the UI private LiveData<Resource<NetworkInformation>> networkInformationLiveData; @Inject SplashScreenViewModel(@NonNull APIClient apiClient, @NonNull Application application) { super(application); this.apiClient = apiClient; // Initializing the observable with empty data networkInfoObservable = new MutableLiveData<Void>(); // Using the Transformation switchMap to listen when the data changes happen, whenever data // changes happen, we update the LiveData object which we are observing in the MainActivity. networkInformationLiveData = Transformations.switchMap(networkInfoObservable, input -> apiClient.getNetworkInformation()); } /** * Function to get LiveData Observable for NetworkInformation class * @return LiveData<Resource<NetworkInformation>> */ public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() { return networkInformationLiveData; } /** * Whenever we want to reload the networkInformationLiveData, we update the mutable LiveData value * which in turn calls the `Transformations.switchMap()` function and updates the data and we get * call back */ public void setNetworkInformation() { networkInfoObservable.setValue(null); } } 

The action code will be updated as -

 final SplashScreenViewModel splashScreenViewModel = ViewModelProviders.of(this, viewModelFactory).get(SplashScreenViewModel.class); observeViewModel(splashScreenViewModel); // This function will ensure that Transformation.switchMap() function is called splashScreenViewModel.setNetworkInformation(); 

See her droidCon NYC video for more information on LiveData. The official Google repository for LiveData https://github.com/googlesamples/android-architecture-components/ search for the GithubBrowserSample project.

The apiClient.getNetworkInformation() call does not need any parameters to obtain additional information. Consequently, "Void" is added to MutableLiveData .

 public LiveData<Resource<NetworkInformation>> getNetworkInformation() { final MutableLiveData<Resource<NetworkInformation>> data = new MutableLiveData<>(); apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() { @Override public void onResponse( @NonNull Call<NetworkInformation> call, @NonNull Response<NetworkInformation> response ) { if (response.body() != null && response.isSuccessful()) { data.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null)); } else { data.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message())); } } @Override public void onFailure(@NonNull Call<NetworkInformation> call, @NonNull Throwable throwable) { data.setValue( new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable)); } }); return data; } 
0
source

I did not encounter the same problem, but I came across a similar thing where the number of observers increased every time I saved the data in db. The way I was debugging was how many instances or different instances of observers were called, and I found out that when you retrieve the current data from the view model, it needs to be checked for non-empty or you can say that only one instance is returned -

 private LiveData<T> data; public LiveData<T> getLiveData(){ if(data ==null){ data = //api call or fetch from db } return data; } 

Before I just returned the data object, and then checking the source code, I came to the conclusion that livingata automatically updates your object and every time without a zero check, a new instance is created, and new observers join. Someone can correct me if my understanding of life is wrong.

0
source

Source: https://habr.com/ru/post/1273700/


All Articles