How to test SingleLiveEvent<Void> variable? - android

I have a SingleLiveEvent<Void> variable. On getting response from api I am making it post. My callback is called and a popup is shown.
My question is how will I write a test case for checking, is my popup shown or not.
Live Event:
private SingleLiveEvent<Void> onAccountOverDrawn = new SingleLiveEvent<>();
On success response I am calling:
onAccountOverDrawn.post();
In my fragment I am registering it as
viewModel.getOnAccountOverDrawn().observe(this, aVoid -> onAccountOverDrawn());
and in onAccountOverDrawn() I am just showing a popup.
So how will I write a test case for this scenario?
Current test case:
#Test
public void updateApplicationStatus_AccountOverdrawn() {
viewModel.updateApplicationStatus("AMOUNT_PENDING");
assertNotNull(viewModel.getOnAccountOverDrawn()); //this line is of no use. Need to change this.
}

I solve this problem like this:
Get LiveData and subscribe our mock-observer to it.
Call the method that should change the LiveData inside the ViewModel.
Check that our mock-observer received the updated data.
Check that there were no more changes of this mock-observer.
Check that if we re-subscribe this mock-observer on the same LiveData then we do not receive data
See code below:
#RunWith(MockitoJUnitRunner.class)
public class SomeFeatureViewModelTest {
private SomeFeatureViewModel mSomeFeatureViewModel;
// Rule for help testing. Just trust me you need it :)
#Rule
public InstantTaskExecutorRule mRule = new InstantTaskExecutorRule();
#Mock
private Observer<Void> mOnClickButtonEventObserver;
#Before
public void setup() {
mSomeFeatureViewModel = new SomeFeatureViewModel();
}
#Test
public void clickOnNextScreenButton() {
// get LiveData and subscribe our observer to it:
mSomeFeatureViewModel.getOnClickButtonEvent().observeForever(mOnClickButtonEventObserver);
// call the method that should change the LiveData inside the ViewModel:
mSomeFeatureViewModel.clickOnNextScreenButton();
// check that our observer received the updated data:
verify(mOnClickButtonEventObserver).onChanged(null);
// check that there were no more changes of this observer:
verifyNoMoreInteractions(mOnClickButtonEventObserver);
// check that if we re-subscribe this observer on the same LiveData then we do not receive data:
mSomeFeatureViewModel.getOnClickButtonEvent().observeForever(mOnClickButtonEventObserver);
verifyNoMoreInteractions(mOnClickButtonEventObserver);
}
}

If You want to test the live data has been observed or not you can use the following test
private LifecycleOwner lifecycleOwner;
MutableLiveData<Boolean> mutableLiveData = new MutableLiveData(); //Live data to be observed from viewModel
#Mock
private Lifecycle lifecycle;
#Before
public void setup() {
lifecycleOwner = getLifecycleOwner();
PowerMockito.when(lifecycle.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
}
#Test
public void test() {
mutableLiveData.observe(lifecycleOwner, new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean aBoolean) {
assertTrue(aBoolean);
}
});
mutableLiveData.postValue(true);
}
private LifecycleOwner getLifecycleOwner() {
return new LifecycleOwner() {
#NonNull
#Override
public Lifecycle getLifecycle() {
return lifecycle;
}
};
}

If you want to test whether there was a pop up or not, you can use Robolectric.

Related

Using Rxjava to insert , delete from room db

i'm working on project where i have to insert and delete data from room db , so basically i was using the old approach which is to implement Asynctask for background operations but since it is no longer recommended , i decided to use Rxjava instead , i tried to implement it but i'm not getting any result so far , and this is a piece of code where it shows the insertion of data
Completable.fromAction(new Action() {
#SuppressLint("CheckResult")
#Override
public void run() throws Exception {
recordingDb.insertRecording(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
And this is the deletion method
public void DeleteData(modelUidd modelUidd) {
Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
recordingDb.delete(modelUidd);
}
}).subscribeOn(Schedulers.io());
}
So basically i tried to use completable with the operator fromaction , i'm not sure if what i implemented is correct or not , any help would appreciated guys , thank you
The problem is that you are actually not subscribing to the observables, so nothing is happening.
To subscribe to an observable, you have to call the .subscribe() method.
I suggest that your methods defined in your DAO classes (or you "repository" classes), such as DeleteData in your example, return the Observable. Then, you can call the method in the DAO to get the Observable and subscribe to it from (ideally) a ViewModel or, if not, directly from an Activity. The moment you call the subscribe you will trigger the actual insertion or deletion, and will get a response from the onSuccess or onError defined callbacks.
For example:
public class MyViewModel extends ViewModel {
private MyRepository myRepository;
private final CompositeDisposable disposables;
#Inject
public MyViewModel(MyRepository myRepository) {
...
this.myRepository = myRepository;
disposables = new CompositeDisposable();
...
}
public void callObservableInRepository() {
disposables.add(myRepository.myObservable()
.subscribe(onSuccess -> {...} , onError -> {...}));
}
#Override
protected void onCleared() {
disposables.clear();
}
}
You can also check these two other answers for more information:
About async operations in RxJava
Using CompositeDisposable in ViewModel

AndroidViewModel - Making duplicate calls doesn't return data in observe function

My question is related to ViewModel second time returns null wherein I am not getting callback inobserve function if I make a repeated call to server. Following is the code I am 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 Activity, the ViewModel is defined as followed -
final NetworkInformationViewModel networkInformationViewModel =
ViewModelProviders.of(this, viewModelFactory).get(NetworkInformationViewModel.class);
observeViewModel(networkInformationViewModel);
The observeViewModel function is used to add observable on 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 following line in above function allows the callback to come everytime, but there has to be a proper way of doing this.
//observeViewModel(networkInformationViewModel);
Please note:-
I don't need RxJava implementation for implementing this.
Right now in getNetworkInformation() you are:
Creating a new LiveData
Updating the the LiveData using setValue
Instead, you should have a single LiveData for APIClient created as a member variable, then in getNetworkInformation() just update that member LiveData.
More generally, your APIClient is a data source. For data sources, you can have them contain member LiveData objects that update when the data changes. You can provide getters to those LiveData objects to make them accessible in ViewModels, and ultimately listen to them in your Activities/Fragments. This is similar how you might take another data source, such as Room, and listen to a LiveData returned by Room.
So the code in this case would look like:
#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 can be the same as you originally coded it; it will just get and observe the LiveData from the ViewModel.
So what is Transformations.switchMap for?
switchMap isn't necessary here because you don't need to change the underlying LiveData instance in APIClient. This is because there's really only one piece of changing data. Let's say instead your APIClient needed 4 different LiveData for some reason, and you wanted to change which LiveData you observed:
public class APIClient {
private MutableLiveData<Resource<NetworkInformation>> mNetData1, mNetData2, mNetData3, mNetData4;
...
}
Then let's say that your fetchNetworkInformation would refer to 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 changes, and you're also using some sort of parameter to determine which LiveData you want. In this case, you'd use a switchMap, because you want to make sure that the observe statement you called in your Activity/Fragment observes the LiveData returned from your APIClient, even if you change the underlying LiveData instance. And you don't want to call observe again.
Now this is a bit of an abstract example, but it's basically what your calls to a Room Dao do -- if you have a Dao method that queries your RoomDatabase based on an id and returns a LiveData, it will return a different LiveData instance based on the id.
I didn't met the same issue, but i came across a similar thing where the number of observers were increasing each time i was saving the data in db. The way i debugged was how many instances or different instances of observers were getting invoked and i came to know that when you are fetching the live data from view model it needs to be checked for non null or you can say only 1 instance is being returned -
private LiveData<T> data;
public LiveData<T> getLiveData(){
if(data ==null){
data = //api call or fetch from db
}
return data;
}
Before i was simply returning the data object and then after checking the source i came to the conclusion that livedata automatically updates your object and everytime without the null check new instance was getting created and new observers were getting attached. Someone can correct me if my understanding regarding livedata is wrong.
I have already updated the linked question's answer. Re-posting here since I have placed a bounty on the question and hopefully someone will verify that this is the proper way to handle the issue.
Following is the 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'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();
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.
The apiClient.getNetworkInformation() call doesn't need it any parameters to get additional information. Hence, the 'Void' added in 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;
}

Actual invocation has different arguments Unit Presenter

I create a unit test for my Presenter. My Presenter implements Listener callback if successfully load data from API (use Interactor):
PresenterTest.java
public class MainContactPresenterTest {
#Mock LoadContactInteractor loadContactInteractor;
#Mock ApiService apiService;
#Mock LoadContactView loadContactView;
#Mock ContactRepository contactRepository;
#Mock LoadContactInteractor.OnLoadDataFinishedListener listener;
#InjectMocks MainContactPresenterImpl presenter;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
public void getContactLists() {
// given
// when
presenter.fetchRemoteContacts();
// then
Mockito.verify(loadContactInteractor).onLoadData(listener);
}
}
Here is my Presenter:
public class MainContactPresenterImpl implements MainContactPresenter,
LoadContactInteractor.OnLoadDataFinishedListener {
private LoadContactView loadContactView;
private LoadContactInteractor loadContactInteractor;
private ContactRepository contactRepository;
#Inject
public MainContactPresenterImpl(LoadContactInteractor loadContactInteractor,
#NonNull LoadContactView loadContactView,
ContactRepository contactRepository) {
this.loadContactView = loadContactView;
this.loadContactInteractor = loadContactInteractor;
this.contactRepository = contactRepository;
}
#Override
public void onSuccessLoad(List<Contact> contacts) {
loadContactView.saveDataToLocalStorage(contacts);
}
#Override
public void onErrorLoad() {
loadContactView.dismissProgress();
loadContactView.showErrorMessage();
}
#Override
public void preCheckCacheData() {
if (contactRepository.getContactCount() == 0) {
// Load contacts from Server
fetchRemoteContacts();
} else {
fetchLocalContacts();
}
}
#Override
public void fetchRemoteContacts() {
loadContactView.showProgress();
loadContactInteractor.onLoadData(this);
}
}
But when I ran test, I got the mocking parameter in verify not match.
I got my presenter that have to be an argument. Not the listener.
Argument(s) are different! Wanted:
loadContactInteractor.onLoadData(
listener
);
Actual invocation has different arguments:
loadContactInteractor.onLoadData(
fanjavaid.gojek.com.contacts.presenter.MainContactPresenterImpl#1757cd72
);
How to handle that? Thank you
You are creating a mock...
#Mock LoadContactInteractor.OnLoadDataFinishedListener listener;
...and then you don't use it ever again and act suprised when verify tells you, that it wasn't actually used. Why? Of course it wasn't used, since you never use it anywhere, so how should your classes know to use that mock object?
Your MainContactPresenterImpl does not use an OnLoadDataFinishedListener as an external dependency (then your could perhaps inject it via #InjectMocks), it is itself such a listener and thus mocking another listener makes no sense here.
In other words, MainContactPresenterImpl has no OnLoadDataFinishedListener field, so Mockito is of course not capable of injecting something in this non-existing field. For something like this to work, you would need to add such a field and then use the content of that field when calling your onLoadData method.
The only invocation of your method is here...
loadContactInteractor.onLoadData(this);
And what is this in that context? It's the MainContactPresenterImpl object that contains the method, in other words, your presenter.
So, what will work is...
Mockito.verify(loadContactInteractor).onLoadData(presenter);

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;
}
}
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.

Making changes to LiveData to "redo" work in ViewModel

So, I have just started experimenting with LiveData - I am busy with a new project, where I am using ViewModel as well as LiveData - with some of the RESTFul services I use to fetch data, they take no parameters and return some data.
A typical setup of the MVVM paradigm with LiveData looks much like this:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
Now when we leave this activity, and go to a new activity, by using an Intent or some other means, and not pressing the back button (So, finalize is not called) - and then come back to MyActivity - we of course don't fetch the users again, as we should still have that data.
However, what if we did want to fetch them again?
The only way to do this properly, from what I have looked at, seems to call "setValue" on the getUsers() LiveData object
Something like this:
public class MyActivity extends AppCompatActivity {
public void onResume() {
viewModel.setActive(true);
}
}
And the ViewModel would look like this:
private final MutableLiveData<Boolean> activeLiveData = new MutableLiveData<>();
ViewModel(ViewModelRepo repo){
this.repo = repo;
results = Transformations.switchMap(activeLiveData, active ->{
if(active){
return repo.getUsers();
}else {
return AbsentLiveData.create(); //"Null live data"
}
});
}
LiveData<Users>> getUsers() {
return results;
}
//This could be called "update" with no params
void setActive(boolean active) {
activeLiveData.setValue(active);
}
The one reason I have decided to do it like this is because Google does not want us doing this:
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS
return repository.getPostCode(address);
}
}
For this reason:
If this is the implementation, the UI would need to unregister from
the previous LiveData and re-register to the new instance each time
they call getPostalCode(). Moreover, if the UI is re-created, it
triggers another call to repository.getPostCode() instead of using the
previous call’s result.
Is there a better way to get the ViewModel to "redo" its repo.getUsers() call? Perhaps I could just make a method that says "Update()" instead of "active" but still - its doing the same thing differently.
Well here you're doing the fetching in the creator of the ViewModel, which locks things in place. Usually they'd advise to fetch the data in the getter, if the data is not there already.
So a good option would be to use the regular pattern first :
private MutableLiveData<Users> users = null;
ViewModel(ViewModelRepo repo){
this.repo = repo;
}
LiveData<Users> getUsers() {
if (users = null) {
fetchUsers();
}
return users;
}
public void fetchUsers() {
users.postValue(repo.getUsers());
}
And then from your Activity/Fragment, whenever you feel necessary to "refresh the users", you'd simply call viewModel.fetchUsers();

Categories

Resources