Search code examples
javaandroidmvvmretrofit2android-architecture-components

How to link Retrofit and Repository/ViewModel for a MVVM pattern?


I have trouble linking Retrofit with MVVM architecture. Indeed, after reading documentation where they only talk about Room which is for SQLite local database, I searched the same but for data that comes from a Rest Server. So, I tried to do something similar to this and it doesn't worked : https://proandroiddev.com/mvvm-architecture-viewmodel-and-livedata-part-1-604f50cda1

I have an Activity that observes a ViewModel :

Activity code :

mFlightPlanViewModel = ViewModelProviders.of(this).get(FlightPlanViewModel.class);
        mFlightPlanViewModel.getFlightPlans().observe(this, (flightPlans) -> {
            Log.d(TAG, "ON_CHANGED");

            mFlightPlanAdapter.setFlightPlans(flightPlans);
        });

The ViewModel :

public class FlightPlanViewModel extends AndroidViewModel {
    private static final String TAG = "FlightPlanViewModel";

    private LiveData<List<FlightPlan>> mFlightPlans;
    private FlightPlanRepository mFlightPlanRepository;

    public FlightPlanViewModel(@NonNull Application application) {
        super(application);
        Log.d(TAG, "CONSTRUCTOR");

        mFlightPlanRepository = FlightPlanRepository.getInstance();
        mFlightPlans = mFlightPlanRepository.getFlightPlans();
    }

    public LiveData<List<FlightPlan>> getFlightPlans() {
        Log.d(TAG, "GET_FLIGHT_PLANS");

        return mFlightPlans;
    }
}

The ViewModel answers the Repository which uses a singleton pattern :

public class FlightPlanRepository {
    private static final String TAG = "FlightPlanRepository";

    private static FlightPlanRepository instance;
    private RestApi mRestApi;

    private FlightPlanRepository() {
        Log.d(TAG, "CONSTRUCTOR");

        mRestApi = RestDao.getRestDao();
    }

    public static FlightPlanRepository getInstance() {
        Log.d(TAG, "GET_INSTANCE");

        if (instance == null) {
            instance = new FlightPlanRepository();
        }
        return instance;
    }

    public MutableLiveData<List<FlightPlan>> getFlightPlans() {
        Log.d(TAG, "GET_FLIGHT_PLANS");

        final MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();

        mRestApi.getFlightPlanList().enqueue(new Callback<List<FlightPlan>>() {
            @Override
            public void onResponse(Call<List<FlightPlan>> call, Response<List<FlightPlan>> response) {
                if (response.code() == 200) {
                    List<FlightPlan> temp = response.body();
                    for (FlightPlan flightPlan : temp) {
                        Log.d(TAG + "res", flightPlan.toString());
                    }
                    data.setValue(response.body());
                    Log.d(TAG + "res", response.toString());
                }
            }

            @Override
            public void onFailure(Call<List<FlightPlan>> call, Throwable t) {
                List<FlightPlan> flightPlans = new ArrayList<>();
                flightPlans.add(new FlightPlan(0, "Test", 3.551, 50.52, 3.55122, 50.52625));
                data.setValue(flightPlans);
                Log.d(TAG, t.getMessage());
            }
        });

        return data;
    }
}

The repository uses a Retrofit instance :

public class RestDao {
    private static final String BASE_URL = "http://192.168.1.78:8080";
    private static Retrofit instance;

    private static Retrofit getInstance() {
        if (instance == null) {
            instance = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return instance;
    }

    public static RestApi getRestDao() {
        return getInstance().create(RestApi.class);
    }
}

It uses this interface :

public interface RestApi {
    @GET("/plan/list")
    Call<List<FlightPlan>> getFlightPlanList();
}

The part of the code that doesn't work is :

public MutableLiveData<List<FlightPlan>> getFlightPlans() {
    Log.d(TAG, "GET_FLIGHT_PLANS");

    final MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();

    mRestApi.getFlightPlanList().enqueue(new Callback<List<FlightPlan>>() {
        @Override
        public void onResponse(Call<List<FlightPlan>> call, Response<List<FlightPlan>> response) {
            if (response.code() == 200) {
                List<FlightPlan> temp = response.body();
                for (FlightPlan flightPlan : temp) {
                    Log.d(TAG + "res", flightPlan.toString());
                }
                data.setValue(response.body());
                Log.d(TAG + "res", response.toString());
            }
        }

        @Override
        public void onFailure(Call<List<FlightPlan>> call, Throwable t) {
            List<FlightPlan> flightPlans = new ArrayList<>();
            flightPlans.add(new FlightPlan(0, "Test", 3.551, 50.52, 3.55122, 50.52625));
            data.setValue(flightPlans);
            Log.d(TAG, t.getMessage());
        }
    });

    return data;
}

This return a null list. I think that I understand why : calling enqueue() method made a request that is in an other Thread so here we return data without waiting for the result.

So my question is how to link Retrofit and my ViewModel?


Solution

  • So, what happens when there is a response if I do that ?

    public LiveData<List<FlightPlan>> getFlightPlans() {
        Log.d(TAG, "GET_FLIGHT_PLANS");
    
        MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();
    
        mRestApi.getFlightPlanList().enqueue(new Callback<List<FlightPlan>>() {
            @Override
            public void onResponse(Call<List<FlightPlan>> call, Response<List<FlightPlan>> response) {
                if (response.code() == 200) {
                    data.setValue(response.body());
                }
            }
    
            @Override
            public void onFailure(Call<List<FlightPlan>> call, Throwable t) {
                // Do something
            }
        });
    
        return data;
    }
    

    I you run that, there is fistly a null object returned but if there is a response, will it return something ?

    I tried that :

    public LiveData<List<FlightPlan>> getFlightPlans() {
        Log.d(TAG, "GET_FLIGHT_PLANS");
    
        MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();
        List<FlightPlan> flightPlans;
    
        try {
            Response<List<FlightPlan>> response = mRestApi.getFlightPlanList().execute();
            if (response.isSuccessful()) {
                flightPlans = response.body();
            } else {
                Log.d(TAG, "Can't get data !");
                throw new Exception("Can't get data !");
            }
        } catch (Exception e) {
            e.printStackTrace();
    
            flightPlans = new ArrayList<>();
            flightPlans.add(new FlightPlan(5, "Test5", 3.551, 50.52, 3.55122, 50.52625));
        }
    
        data.setValue(flightPlans);
        Log.d(TAG, data.getValue().toString());
    
        return data;
    }
    

    Now it crashes on the execute line because I launch that in the main thread.. So I have to made an async task but where ?

    Other option : Make an async task like at first, put a setter to the model view object and when there is a response I call the setter.. Is that good to do ?

    Thanks for the aswers you gave me ! It was very helpful.

    EDIT : Why is there this code on Google Documentation :

    public class UserRepository {
        private Webservice webservice;
        // ...
        public LiveData<User> getUser(int userId) {
            // This isn't an optimal implementation. We'll fix it later.
            final MutableLiveData<User> data = new MutableLiveData<>();
            webservice.getUser(userId).enqueue(new Callback<User>() {
                @Override
                public void onResponse(Call<User> call, Response<User> response) {
                    data.setValue(response.body());
                }
    
                // Error case is left out for brevity.
            });
            return data;
        }
    }
    

    It means that it's ok to do such thing ?

    SOLUTION !

    EDIT :

    How to connect ViewModel with Repository so that data is propagated to the View (MVVM, Livedata)

    This helps me a lot !!!

    I found the solution ! I'm stupid : I insert old dependencies in the gradle file !