Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Observing viewmodel for the second time returns null in android

In my android app,im following architecture components with mvvm pattern. my app makes a network call to display the weather information.api call is being made from repository which returns a livedata of response to the viewmodel,which inturn is observed by my main activity.

the app works fine except for one condition,whenever i disconnect the internet to test the fail case,it inflates error view as required

in the error view i have a retry button,which makes the method call to observe the viewmodel again(this method was also called by oncreate() for the first time,which worked)

even after switching on the internet,and clicking the retry button which listens for the observable.still the data becomes null.

i dont know why.please anyone help

REPOSITORY

@Singleton public class ContentRepository {

@Inject AppUtils mAppUtils;
private RESTService mApiService;

@Inject public ContentRepository(RESTService mApiService) {
 this.mApiService = mApiService;
}

 public MutableLiveData<ApiResponse<WeatherModel>> getWeatherListData() {
final MutableLiveData<ApiResponse<WeatherModel>> weatherListData = new                     MutableLiveData<>();
  mApiService.getWeatherList().enqueue(new Callback<WeatherModel>() {
  @Override public void onResponse(Call<WeatherModel> call,                          Response<WeatherModel> response) {
    weatherListData.setValue(new ApiResponse<>(response.body()));
  }

  @Override public void onFailure(Call<WeatherModel> call, Throwable t) {
    weatherListData.setValue(new ApiResponse<>(t));
  }
});
return weatherListData;
}
}

VIEWMODEL

public class HomeViewModel extends AndroidViewModel {

private final LiveData<ApiResponse<WeatherModel>> weatherListObservable;

 @Inject public HomeViewModel(Application application, ContentRepository contentRepository) {
super(application);
this.weatherListObservable = contentRepository.getWeatherListData();
}

 public LiveData<ApiResponse<WeatherModel>> getWeatherListObservable() {
return weatherListObservable;
}
}

OBSERVE METHOD IN ACTIVITY

private void observeViewModel() {
mHomeViewModel = ViewModelProviders.of(this, mViewModelFactory).get(HomeViewModel.class);
mHomeViewModel.getWeatherListObservable().observe(this, weatherModelApiResponse -> {
  if (weatherModelApiResponse.isSuccessful()) {
    mErrorView.setVisibility(View.GONE);
    mBinding.ivLoading.setVisibility(View.GONE);
    try {
      setDataToViews(weatherModelApiResponse.getData());
    } catch (ParseException e) {
      e.printStackTrace();
    }
  } else if (!weatherModelApiResponse.isSuccessful()) {
    mBinding.ivLoading.setVisibility(View.GONE);
    mDialogUtils.showToast(this, weatherModelApiResponse.getError().getMessage());
    mErrorView.setVisibility(View.VISIBLE);
  }
});
}

RETRY BUTTON IN ACTIVITY

@Override public void onClick(View v) {
switch (v.getId()) {
  case R.id.btn_retry:
    mErrorView.setVisibility(View.GONE);
    observeViewModel();
    break;
}
}
like image 562
al_mukthar Avatar asked Oct 08 '17 04:10

al_mukthar


People also ask

Can Livedata values be null?

LiveData is written in Java. It does allow setting it's value to null .

Can we use same ViewModel for two activities?

In android, we can use ViewModel to share data between various fragments or activities by sharing the same ViewModel among all the fragments and they can access everything defined in the ViewModel. This is one way to have communication between fragments or activities.

What happens to ViewModel when activity is destroyed?

E.g. if it is an Activity, until it is finished. In other words, this means that a ViewModel will not be destroyed if its owner is destroyed for a configuration change (e.g. rotation). The new instance of the owner will just re-connected to the existing ViewModel.

How does a ViewModel retain itself?

ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance. FYI: You can use ViewModel to preserve UI state only during a configuration change, nothing else as explained perfectly in this official doc.


1 Answers

Updated:- 5 December 2017

I was fortunate to meet Lyla Fujiwara, during Google Developer Days, India where I asked her the same question. She suggested me to user Transformations.switchMap(). Following is the updated 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's 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 Activity's 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();

This looks the most prominent and proper solution to me for now, I will update the answer if I better solution later.

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

Old Code

I have not been able find a proper solution to this, but this works so far - Declare ViewModel outside the observeViewModel() and change the function like this -

private void observeViewModel(final HomeViewModel homeViewModel) {
homeViewModel.getWeatherListObservable().observe(this, weatherModelApiResponse -> {
  if (weatherModelApiResponse.isSuccessful()) {
    mErrorView.setVisibility(View.GONE);
    mBinding.ivLoading.setVisibility(View.GONE);
    try {
      setDataToViews(weatherModelApiResponse.getData());
    } catch (ParseException e) {
      e.printStackTrace();
    }
  } else if (!weatherModelApiResponse.isSuccessful()) {
    mBinding.ivLoading.setVisibility(View.GONE);
    mDialogUtils.showToast(this, weatherModelApiResponse.getError().getMessage());
    mErrorView.setVisibility(View.VISIBLE);
  }
});
}

Update HomeViewModel to -

public class HomeViewModel extends AndroidViewModel {

private final LiveData<ApiResponse<WeatherModel>> weatherListObservable;

@Inject public HomeViewModel(Application application, ContentRepository contentRepository) {
super(application);
getWeattherListData();
}

public void getWeatherListData() {
this.weatherListObservable = contentRepository.getWeatherListData();
}
public LiveData<ApiResponse<WeatherModel>> getWeatherListObservable() {
return weatherListObservable;
}

}

Now Retry button, call the observeViewModel function again and pass mHomeViewModel to it. Now you should be able to get a response.

like image 173
Rohan Kandwal Avatar answered Sep 22 '22 07:09

Rohan Kandwal