Search code examples
javaandroidretrofit2rx-java2android-viewmodel

Send specific request multiple times with different parameters using Retrofit & RxJava


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.


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