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?
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 !