I am using MVVM + LiveData + Dagger 2.11 on my app.On SignInFragment click on a textview send request to server and show respopnse on snackbar. It works fine on first time click of textview.If I click again,it sends request(inbetween shows snackbar response message here) and ViewModel MediatorLiveData observer onChanged method called muliple times.Is it default behaviour of MediatorLiveData?
SignInViewModel.java
public class SignInViewModel extends AndroidViewModel {
@Inject
MediatorLiveData mediatorLiveData;
@Inject
SnackbarMessage mSnackbarTextLiveData = new SnackbarMessage();
@Inject
public SignInViewModel(Application application,SignInRepository signInRepository) {
super(application);
this.signInRepository = signInRepository;
}
public MediatorLiveData<ResendActivationCodeResponse> resendActivationCode(final String phoneNumber, final String countryCode) {
final MutableLiveData<NetworkResponse> connectViaPhoneResponseMutableLiveData = signInRepository.resendActivationCode(phoneNumber, countryCode);
mediatorLiveData.addSource(connectViaPhoneResponseMutableLiveData, new NetworkResponseObserver() {
@Override
public void onSuccess(Object data) {
mediatorLiveData.setValue(data);
}
@Override
public void onBadRequest(Object data, String errorMessage) {
mSnackbarTextLiveData.setValue(errorMessage);
}
@Override
public void onUnAuthorisedError(Object data) {
mSnackbarTextLiveData.setValue(data.toString());
}
@Override
public void onFailure(Object data, String errorMessage) {
mSnackbarTextLiveData.setValue(errorMessage);
}
@Override
public void onNoNetworkAvailable(Object data, String errorMessage) {
mSnackbarTextLiveData.setValue(data.toString());
}
@Override
public void onLoading(Object data) {
}
});
return mediatorLiveData;
}
}
SignInFragment.java
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mSignInViewModel = ViewModelProviders.of(mActivity, mViewModelFactory).get(SignInViewModel.class);
setupSnackbar();
}
private void setupSnackbar() {
mSignInViewModel.getSnackbarMessage().observe(this, new SnackbarMessage.SnackbarObserver() {
@Override
public void onNewMessage(String snackbarMessage) {
ActivityUtils.showSnackbar(getView(), snackbarMessage);
}
});
}
@OnClick(R.id.resend_activation_code_textview)
public void reSendActivationCode() {
showProgress(true);
final MediatorLiveData<ResendActivationCodeResponse> resendActivationCodeResponseMediatorLiveData = mSignInViewModel.resendActivationCode(mPhoneNumber, mCountryCode);
Observer<ResendActivationCodeResponse> resendActivationCodeResponseObserver = new Observer<ResendActivationCodeResponse>() {
@Override
public void onChanged(@Nullable ResendActivationCodeResponse resendActivationCodeResponse) {
if (resendActivationCodeResponse != null) {
showProgress(false);
ActivityUtils.showSnackbar(getView(), activationCodeResentMessage);
//resendActivationCodeResponseMediatorLiveData.removeObserver(this);
}
}
};
resendActivationCodeResponseMediatorLiveData.observe(PhoneNumberActivationFragment.this, resendActivationCodeResponseObserver);
}
It looks like you're calling addSource
with different LiveData
associated with different phone numbers every time your resend_activation_code_textview
is clicked. These different LiveData
sources are also all associated with different NetworkResponseObservers
, that call setValue()
. setValue()
is what updates your listening fragments and what is what is called too many times.
I believe the issue is because you call addSource
every time resend_activation_code_textview is clicked and you never remove any sources.
If you click resend_activation_code_textview
10 times, your mediatorLiveData
will have 10 different sources, when you probably only wanted one.
When a source is added, it does an initial trigger of your mediatorLiveData
, so you will always trigger setValue()
at least once. When any of the 10 added sources are updated, it will also update your mediatorLiveData
, and call setValue()
. Depending on what signInRepository.resendActivationCode
does and if it updates any of the other 10 LiveData sources, this will trigger multiple setValue()
calls for one click.
There's a removeSource() method which you could call to make sure you never have more than one source at a time, this likely getting rid of the multiple onChanged calls. But there's a built in solution for what I think you're trying to do (which uses MediatorLiveData under the hood) -- it's the switchMap Transformation.
switchMap
allows you to change the underlying source that a LiveData
is listening to, without updating the observers. So instead of clicking resend_activation_code_textview
10 times and adding 10 different sources, you can have it so that every time you click resend_activation_code_textview
the previous source will be swapped for a new source.
The example scenario for switchMap
is that you have a method to look up a userById()
. You make a normal LiveData to store the user id, and then use the switchMap
transformation so that you have another LiveData for the current user. As the id changes, the current user is swapped out and updated:
MutableLiveData userIdLiveData = ...;
LiveData userLiveData = Transformations.switchMap(userIdLiveData, id ->
repository.getUserById(id));
void setUserId(String userId) {
this.userIdLiveData.setValue(userId);
}
I think you're doing something similar with the phone number and country code. This is like your "id". You'll want to create an object which contains a phone number and country code, let's call it FullPhoneNumber
. Then you'll make a LiveData<FullPhoneNumber> phoneNumberLiveData
, which is similar to the userIdLiveData
in the previous example. Then:
LiveData<ResendActivationCodeResponse> reactivationLiveData =
Transformations.switchMap(phoneNumberLiveData, currentPhoneNumber ->
signInRepository.resendActivationCode(currentPhoneNumber.getNumber(), currentPhoneNumber.getCountryCode());
Hope that helps or at least points you in the right direction!