Search code examples
androidmvvmretrofit2android-viewmodel

onChanged() is triggered before the network response is logged by the HttpLoggingInterceptor


I am trying to get a hang of the MVVM architecture in Android and was working on a sample app for the same. I have avoided the Repository class for now, it is avoiding separation of concerns but I wanted to get better understanding of ViewModel and LiveData first. I have a fragment where I make a network call using retrofit and a viewmodel instance. Then using the same viewmodel object I make the same call but with different parameter values. Now onChanged is triggered and when I log the response (using log.d) I get the old response inside it. And after a delay of a few milliseconds, the HttpLoggingInterceptor logs the new response. I have tried a lot of things but I cannot figure out what is happening. Upon searching for this issue, I found this: Retrofit subsequent calls not working, but I don't understand how to implement the given answer.

CODE:
ApiInterface

public interface ApiInterface {
@GET("fetch")
Call<String> fetchData(@Query("param1") String param1,
                       @Query("param2") String param2,
                       @Query("param3") String param3,
                       @Query("param4") String param4,
                       @Query("param5") String param5);

ViewModelData

public class ViewModelData extends ViewModel {
private MutableLiveData<String> data = new MutableLiveData<>();

private final String TAG = getClass().getSimpleName();

public LiveData<String> getData(String param1, String param2, String param3, String param4,
                                        String param5){
    loadData(param1, param2, param3, param4, param5);

    return data;
}

private void loadData(String param1, String param2, String param3, String param4,
                              String param5){
    Retrofit retrofit = HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
    logging.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient okHttpClientGet = new OkHttpClient.Builder()
            .addInterceptor(logging)
            .build();

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(ScalarsConverterFactory.create())
            .client(okHttpClientGet)
            .build();
    ApiInterface apiInterface = retrofit.create(ApiInterface.class);

    Call<String> call = apiInterface.fetchData(param1, param2, param3, param4, param5);

    call.enqueue(new Callback<String>() {
        @Override
        public void onResponse(Call<String> call, Response<String> response) {
            if(response.isSuccessful()){
                Log.v(TAG, "onResponse: " + response.body());
                data.postValue(response.body());
            }else{
                try {
                    String errorBody = response.errorBody().string();
                    Log.e(TAG, "onResponse: " + errorBody);
                    data.postValue(errorBody);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

        @Override
        public void onFailure(Call<String> call, Throwable t) {
            Log.e(TAG, "onFailure: " + t.getMessage());
        }
    });
}

FragmentTest (this is inside a function I defined which is being called upon the click of a button)

viewmodelData.getData("param1", "param2", "param3",
            "param4", "param5")
                .observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.v("TAG", "onChanged: " + s);

                Gson gson = new Gson();
                JsonObject root = gson.fromJson(s, JsonObject.class);
                
                //parsing response...
            }
        });

ViewModel instance has been created as:

viewmodelData = new ViewModelProvider(this).get(ViewModelData.class);

I would appreciate any help as I have been stuck on this for some time now.


Solution

  • Your flow is adding multiple observers over the same LiveData, and this might causing the issue you see.

    On each button click you are observing (again again) the LiveData object returned by getData().

    Change your getData method to be:

    public LiveData<String> getData() {
        return data;
    }
    

    And make your loadData method public:

    public void loadData(String param1, String param2, String param3, String param4, String param5) {
       ...
    }
    

    In your fragment's onCreateView() add an observer for your LiveData:

    viewmodelData
        .getData()
        .observe(getViewLifecycleOwner(), new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.v("TAG", "onChanged: " + s);
    
                Gson gson = new Gson();
                JsonObject root = gson.fromJson(s, JsonObject.class);
                
                //parsing response...
            }
        });
    

    This ensures your LiveData is only observed & handled by a single observer.

    Upon button click, call:

    viewmodelData.loadData("param1", "param2", "param3", "param4", "param5");