I have n EditTexts with their own texts:
editText1 -> text: 13
editText2 -> text: 15
editText3 -> text: 20
...
And there is an API method named as getNewValue(String currentValue)
. It will get the new value of each EditText due to the current value.
Scenario:
getNewValue()
request will be sent if each EditText get focused and the new value will be set on focused EditText.
How can I achieve this goal using Retrofit, RxJava and ViewModel?
I tried this code:
Fragment:
editText1.setOnFocusChangeListener(getEtFocusChangeListener(editText1.getText().toString()));
editText2.setOnFocusChangeListener(getEtFocusChangeListener(editText2.getText().toString()));
editText3.setOnFocusChangeListener(getEtFocusChangeListener(editText3.getText().toString()));
private View.OnFocusChangeListener getEtFocusChangeListener(String currentValue) {
return (view, hasFocus) -> {
if (hasFocus) {
EditText et = (EditText) v;
viewModel.getNewValue(currentValue);
viewModel.getNewValueResponse().observe(getViewLifecycleOwner(), newValue -> et.setText(newValue));
viewModel.getNewValueError().observe(getViewLifecycleOwner(), throwable -> Log.i(TAG, "New Value Error -----> " + throwable));
}
};
}
ViewModel:
private final MutableLiveData<String> newValueResponse = new MutableLiveData<>();
private final MutableLiveData<Throwable> newValueError = new MutableLiveData<>();
public void getNewValue(String currentValue) {
apiService.getNewValue(currentValue)
.subscribeOn(Schedulers.io())
.subscribe(new SingleObserver<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
compositeDisposable.add(d);
}
@Override
public void onSuccess(@NonNull String newValue) {
newValueResponse.postValue(newValue);
}
@Override
public void onError(@NonNull Throwable throwable) {
newValueError.postValue(throwable);
}
});
}
public LiveData<String> getNewValueResponse() {
return newValueResponse;
}
public LiveData<Throwable> getNewValueError() {
return newValueError;
}
ApiService:
@GET("Values/GetNewValue")
Single<String> getNewValue(@Query("currentValue") String currentValue);
One solution is to declare a global variable to save the the last focused EditText
and use it when the request respond, but I think there is a better & smarter solution.
I am not very familiar with Android programing, so sorry if I am missing something and make any mistake, but why don't you pass a reference to the EditText
that has been focused to the getNewValue
method?
Consider for instance:
private final MutableLiveData<Pair<EditText, String>> newValueResponse = new MutableLiveData<>();
private final MutableLiveData<air<EditText, Throwable>> newValueError = new MutableLiveData<>();
//...
public void getNewValue(final EditText editText, final String currentValue) {
apiService.getNewValue(currentValue)
.subscribeOn(Schedulers.io())
.subscribe(new SingleObserver<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
compositeDisposable.add(d);
}
@Override
public void onSuccess(@NonNull String newValue) {
newValueResponse.postValue(Pair.create(editText, newValue));
}
@Override
public void onError(@NonNull Throwable throwable) {
newValueError.postValue(Pair.create(editText, throwable));
}
});
}
public LiveData<Pair<EditText, String>> getNewValueResponse() {
return newValueResponse;
}
public LiveData<Pair<EditText, Throwable>> getNewValueError() {
return newValueError;
}
The OnFocusChangeListener
will look like the following:
private View.OnFocusChangeListener getEtFocusChangeListener(String currentValue) {
return (view, hasFocus) -> {
if (hasFocus) {
EditText et = (EditText) v;
viewModel.getNewValue(et, currentValue);
viewModel.getNewValueResponse().observe(getViewLifecycleOwner(), newValue -> newValue.first.setText(newValue.second));
viewModel.getNewValueError().observe(getViewLifecycleOwner(), throwable -> Log.i(TAG, "New Value Error -----> " + throwable.second));
}
};
}
In a certain way I found this last code fragment strange, because if you thing about it, due to the asynchronous nature of the API call, the actual result can be for a different EditText
. Perhaps the problem could me mitigated with something like that:
private View.OnFocusChangeListener getEtFocusChangeListener(String currentValue) {
return (view, hasFocus) -> {
if (hasFocus) {
EditText et = (EditText) v;
viewModel.getNewValue(et, currentValue);
viewModel.getNewValueResponse().observe(getViewLifecycleOwner(), newValue -> {
// Only perform the operation if the `EditText` is the affected one
if (et.equals(newValue.first)) {
newValue.first.setText(newValue.second);
}
});
viewModel.getNewValueError().observe(getViewLifecycleOwner(), throwable -> {
if (et.equals(throwable.first)) {
Log.i(TAG, "New Value Error -----> " + throwable.second);
}
});
}
};
}
In my opinion, according to the use case, you could get rid of the newValueResponse
and newValueError
MutableLiveData
instances. For example:
public void getNewValue(final EditText editText, final String currentValue) {
apiService.getNewValue(currentValue)
.subscribeOn(Schedulers.io())
.subscribe(new SingleObserver<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
compositeDisposable.add(d);
}
@Override
public void onSuccess(@NonNull String newValue) {
editText.seText(newValue);
}
@Override
public void onError(@NonNull Throwable throwable) {
Log.i(TAG, "New Value Error -----> " + throwable);
}
});
}
And:
private View.OnFocusChangeListener getEtFocusChangeListener(String currentValue) {
return (view, hasFocus) -> {
if (hasFocus) {
EditText et = (EditText) v;
viewModel.getNewValue(et, currentValue);
}
};
}
Or perhaps more semantically:
public void getNewValue(final String currentValue) {
return apiService.getNewValue(currentValue)
.subscribeOn(Schedulers.io());
}
private View.OnFocusChangeListener getEtFocusChangeListener(String currentValue) {
return (view, hasFocus) -> {
if (hasFocus) {
EditText et = (EditText) v;
viewModel.getNewValue(et, currentValue)
.subscribe(new SingleObserver<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
compositeDisposable.add(d);
}
@Override
public void onSuccess(@NonNull String newValue) {
et.seText(newValue);
}
@Override
public void onError(@NonNull Throwable throwable) {
Log.i(TAG, "New Value Error -----> " + throwable);
}
});
}
};
}