I am new to Android Arch components. I am trying to make a basic todo app using Android View Model and Live Data. What is the best way to make network calls when following MVVM pattern? I need to show a progress bar when a network request starts and dismiss it when the call is complete and in case of errors I need to display a snackbar with the relevant error message. Is it possible to do this without using an AsyncTask?
Remote Repository Class:
public class RemoteRepository {
private APIService apiService;
public RemoteRepository (APIService apiService) {
this.apiService= apiService;
}
public LiveData<List<Project>> getList(String userId) {
final MutableLiveData<List<Project>> data = new MutableLiveData<>();
apiService.getList(userId).enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
data.setValue(response.body());
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
// What to do to show snackbar in activity
}
});
return data;
}
Do we need to use an async task and manage the UI in its preExecute and postExecute callbacks? Or is it possible to do it with Observable Livedata objects?
You can do it this way.
Create a class that has a Throwable and desire success result object.
create a constructor or getter setter method to set values. ProjectRepoClass is an example of it.
Project Repo Class::
public class ProjectRepoModel {
private List<Project> mList;
private Throwable mThrowable;
public ProjectRepoModel (List<Project> mList) {
mList= mList;
}
public ProjectRepoModel (Throwable throwable) {
mThrowable = throwable;
}
public List<Project> getList() {
return mList;
}
public Throwable getThrowable() {
return mThrowable;
}
}
Set value according to API result. it can be an error or success response and return it.
Return data:
public LiveData<List<Project>> getList(String userId) {
final MutableLiveData<ProjectRepoModel > data = new MutableLiveData<>();
apiService.getList(userId).enqueue(new Callback<List<Project>>() {
#Override
public void onResponse(Call<List<Project>> call, Response<List<Project>> response) {
data .setValue( new ProjectRepoModel (response.body()));
}
#Override
public void onFailure(Call<List<Project>> call, Throwable t) {
data .setValue( new ProjectRepoModel (t));
}
});
return data;
}
Create Observable for live data in the UI part and make a check for error and display results according to it.
Observe result In UI Like this:
if (mModel.getThrowable() != null)
{
// Show error
} else {
// display success result
}
This how you can achieve the error handle from the repository in MVVM.
Related
hello i have two problems regarding live data with view model and navigation component first one is when i go from fragment A with live data to fragment B and then from B to A the data in my list gets duplicated,
the other problem is one i re call viewModel.loadList() in my fragment after making and event to filter the data also gets duplicated
here is my view model
public class HomeViewModel extends ViewModel {
MutableLiveData<ArrayList<HomeResponseModel>> homeLiveData = new MutableLiveData<>();
ArrayList<HomeResponseModel> homeList = new ArrayList<>();
public MutableLiveData<ArrayList<HomeResponseModel>> geHomeList(HomeRequestModel homeRequestModel, Context context, ApiInterface apiInterface, LottieAnimationView lottieAnimationView) {
if (homeLiveData == null) {
homeLiveData = new MutableLiveData<ArrayList<HomeResponseModel>>();
loadHomeList(homeRequestModel);
}
return homeLiveData;
}
public void loadHomeList(HomeRequestModel homeRequestModel) {
Call<List<HomeResponseModel>> call = apiInterface.getHomeList(homeRequestModel, );
call.enqueue(new Callback<List<HomeResponseModel>>() {
#Override
public void onResponse(Call<List<HomeResponseModel>> call, Response<List<HomeResponseModel>> response) {
if (response.isSuccessful()) {
homeList.addAll(response.body());
homeLiveData.setValue(homeList);
}
}
#Override
public void onFailure(Call<List<HomeResponseModel>> call, Throwable t) {
}
});
}
my observer in onCreateView
viewModel.geHomeList(homeRequestModel).observe(getViewLifecycleOwner(), new Observer<ArrayList<HomeResponseModel>>() {
#Override
public void onChanged(ArrayList<HomeResponseModel> homeResponse) {
homeResponseModels.addAll(homeResponse);
homeAdapter.notifyDataSetChanged();
}
});
}
how i recall load method after a filter event
viewModel.loadHomeList(homeRequestModel);
Clear the list before adding the new models:
viewModel.geHomeList(homeRequestModel).observe(getViewLifecycleOwner(), new Observer<ArrayList<HomeResponseModel>>() {
#Override
public void onChanged(ArrayList<HomeResponseModel> homeResponse) {
homeResponseModels.clear();
homeResponseModels.addAll(homeResponse);
homeAdapter.notifyDataSetChanged();
}
});
}
Or, even better:
In case your adapter holds a List or HomeResponseModel you could create a method to update it:
public update(List<HomeResponseModel> homeResponse) {
this.homeResponseModels = homeResponse;
notifydatasetchanged();
}
and then change the observe method to call it:
viewModel.geHomeList(homeRequestModel).observe(getViewLifecycleOwner(), new Observer<ArrayList<HomeResponseModel>>() {
#Override
public void onChanged(ArrayList<HomeResponseModel> homeResponse) {
homeAdapter.update(homeResponse);
}
});
}
Besides that, in your ViewModel in the loadHomeList method in the onResponse callback you could assign the data received to the liveData:
if (response.isSuccessful()) {
homeLiveData.setValue(response.body());
}
no need to save it in the homeList var, you can get rid of that var. Otherwise, perform homeList.clear(); before adding to it all the received data to avoid duplicates.
Im working on mvvm design but OnResponse is not saving the data in List. İts returning the emtpy List array. I cant reach the valued List. I realy dont know where is the incorrect piece of code. Here is the code.Help please.
public class RetroClass {
private static final String BASE_URL="--";
private List<ProductModel> productList=new ArrayList<>();
public static Retrofit getRetroInstance(){
return new Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build();
}
public static APIService getAPIService(){
return getRetroInstance().create(APIService.class);
}
public List<ProductModel> getProducts(){
APIService apiService=getAPIService();
apiService.getProducts().enqueue(new Callback<List<ProductModel>>() {
#Override
public void onResponse(Call<List<ProductModel>> call, Response<List<ProductModel>> response) {
productList.addAll(response.body());
for (int k=0;k<productList.size();k++) {
Log.d("onResponse: ", productList.get(k).getOrderName());//im getting the value here
}
}
#Override
public void onFailure(Call<List<ProductModel>> call, Throwable t) {
Log.d("onFailure: ",t.getMessage());
}
});
return productList;//but this is empty
}
}
Here is my view model.
public class ProductsVievModal extends ViewModel {
List<ProductModel> productList;
LiveData<List<ProductModel>> liveproductList;
RetroClass apiClass=new RetroClass();
public List<ProductModel> getProducts(){
productList=apiClass.getProducts();
for (int k=0;k<productList.size();k++) {
Log.d("onResponse: ", productList.get(k).getOrderName());
}
return productList;
}
}
.enqueue is asynchronously sending the request and notify callback of its response. It is asynchronous. onResponse() must complete before you return the product list.
I suspect the return productList; is executed before the onResponse() returned its value. Can you check by putting a log before return productList; to see which line is executed first?
Your data IS saved, but the moment your code needs it the .enqueue is probably not done retrieving the data yet. so it shows as empty
Easiest way to fix this is by having your follow up code in the onResponse so your basicly stuck waiting untill it either retrieved the information or failed
so something like this
public void onResponse(Call<List<ProductModel>> call, Response<List<ProductModel>> response) {
productList.addAll(response.body());
nextFunction();
}
}
#Override
public void onFailure(Call<List<ProductModel>> call, Throwable t) {
Log.d("onFailure: ",t.getMessage());
failFunction();
}
```
I use RxJava in my Android project, I want to retrieve all entities User from a table using RxJava asynchronously and return the list List to an adapter myAdapter(Context context, List<User> users).
But now I can only have Single<User>, how can I get the list directly and put it into my adapter ?
My code:
// here vm.getUserList() should return a List<User>
MyAdapter adapter = new MyAdapter(context, vm.getUserList());
...
And in my vm:
public Single<List<User>> getUserList() {
return Single.fromCallable(() -> myDatabaseRepository.getUsers());
}
You only need to call the users method and handle the success/error in the corresponding method.
getUserList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess(new Consumer<List<User>>() {
#Override
public void accept(List<User> users) throws Exception {
// fill adapter
MyAdapter adapter = new MyAdapter(context, users);
}
})
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
// handle error
}
});
Yes, this is the normal way of retrieving the data from the database/network. You expose from the repository the Observables/Singles, then you subscribe to them and receive the data in the success/error methods to show in the UI to the user.
private void fetchUser() {
('function returning Single<List<User>>').subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSuccess, this::onFail);
}
private void onSuccess(List<User> users) {
//handle user model here
}
private void onFail(Throwable t) {
//handle error here
}
I'm here with a new question about Android and Retrofit. I was wondering what is the correct way to handle multiple async calls on a Android Activity and each of this one, onResponse returns a value used by the next call, because if I understood fine, the calls runs on background which means that if the call didn't finish you returned value will be null until you get a successful response.
I was thinking to achieve that with something like this(only the basis):
private List<SomeModel> mylist;
final Call<List<SomeModel>> call1 = client.getSomeValues1();
final Call<List<SomeModel2>> call2 = client.getSomeValues2();
call1.enqueue(new Callback<SomeModel>() {
#Override
public void onResponse(SomeModel> call, Response<SomeModel> response) {
if (response.isSuccessful()) {
// Set mylist to response.body()
mylist = response.body();
}
}
#Override
public void onFailure(Call<SomeModel> call, Throwable t) {
mylist = null;
}
});
call2.enqueue(new Callback<SomeModel2>() {
#Override
public void onResponse(SomeModel2> call, Response<SomeModel2> response) {
if (response.isSuccessful()) {
// Do something with my list and also with call2 response
if(mylist != null) {
for (SomeModel singleObject: mylist) {
// Do something with each object
}
}
}
}
#Override
public void onFailure(Call<SomeModel2> call, Throwable t) {
// Do something with fail call
}
});
With something like the example before because the calls are running on background and maybe the call2 finish first then mylist value will be null because call1 hasn't finished yet.
Also I was thinking to put call2 inside call1 onResponse but I don't feel that right. I have to say that I'm still learning, I'm pretty rookie.
So, What is the correct why to handle this and how? Thanks. I hope my question is understandable.
Thanks for your suggestion #insa_c
I achieved that with RXJava2, here is how I did it explained general way:
First you need to define your api services as Observables
#GET("url-1/")
Observable<List<Model1>> service1();
#GET("url-2/")
Observable<List<Model2>> service2();
Now in your activity make the calls this way:
final Observable<List<Model1>> call1 = APIClient.getClient().create(APIService.class).service1();
final Observable<List<Model2>> call2 = APIClient.getClient().create(APIService.class).service2();
Now lets make an unique observable with zip (I used a List because I'm adding two different kind of objects to the list that I'll get when both calls finish):
<Observable<List<Object>> test = Observable.zip(call1, call2, new BiFunction<List<Model1>, List<Model2>, List<Object>>() {
#Override
public List<Object> apply(List<Model1> objects1, List<Model2> objects2) throws Exception {
List<Object> list = new ArrayList<>();
list.addAll(objects1);
list.addAll(objects2);
return list;
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
Now the only thing left to do is to subscribe and decide what you want to do with the content of both responses:
test.subscribe(new Observer<List<?>>() {
#Override
public void onSubscribe(Disposable d) {
Log.d("TAG", "onSubscribe");
}
#Override
public void onNext(List<?> objects) {
// You can split the list by object in here with an instanceof to determine if it's Model1 or Model2
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onComplete() {
// Do something here, both calls finished, if you stored something in onNext method you can use it here.
}
});
I hope this info will be helpful for someone.
So I'll try to keep this question as to-the-point as possible, but it will involve code snippets that traverse an entire codepath.
For context, I am fairly new and completely self-taught for Android dev, so please notify me of any clear misunderstandings/poor organization throughout. The main focus of the question is bug I am experiencing now, which is that, after a network request, the variable that was supposed to be set as a result of that network request is null, because the code moved forward before the network request completed.
Here is my activity method. It is supposed to populate the mFriends variable with the result of mUserPresenter.getUserList(), which is (unfortunately) null:
/**
* Grabs a list of friends, populates list with UserAdapter
*/
#Override
public void onResume(){
super.onResume();
mUserPresenter = new UserPresenter();
mFriends = mUserPresenter.getUserList();
if (mGridView.getAdapter() == null) {
UserAdapter adapter = new UserAdapter(getActivity(), mFriends);
mGridView.setAdapter(adapter);
}
else{
((UserAdapter)mGridView.getAdapter()).refill(mFriends);
}
}
Here is how I am structuring my UserPresenter method getUserList:
public List<User> getUserList()
{
ApiService.get_friends(this);
return mUserList;
}
The real magic happens in the ApiService class:
public static void get_friends(final UserPresenter userPresenter){
ApiEndpointInterface apiService = prepareService();
apiService.get_friends().
observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Action1<List<User>>()
{
#Override
public void call(List<User> users) {
userPresenter.setList(users);
}
}
);
}
My thinking was, that by calling userPresenter.setList(users) in ApiService, that would set mUserList to the response from the api request. However, instead, mUserList == null at the time that getUserList responds.
Any ideas of how I can structure this?
I have also started to learn something similar. Here, I would rather use callbacks.
In your presenter,
public void setList(List<User> users) {
yourView.setUserList(users);
}
And your activity which implements a view (MVP)
#Override
public void setUserList(List<User> users) {
((UserAdapter)mGridView.getAdapter()).refill(mFriends);
}
Also, check that retrofit is not returning null list.
I have a made a small app when I was learning about all this. It fetches user data from GitHub and shows in a list. I was also working with ORMLite and Picasso so some db stuff is there. Dagger Dependency is also used (but you can ignore that). Here's the link.
Here's how my Presenter behaves:
private DataRetrieverImpl dataRetriever;
#Override
public void getUserList(String name) {
dataRetriever.getUserList(name);
}
#Override
public void onEvent(DataRetrieverEvent event) {
UserList userList = (UserList)event.getData();
mainView.setItems(userList);
}
DataRetrieverImpl works as a module (sort of).
private DataRetriever dataRetriever;
restAdapter = new RestAdapter.Builder().setEndpoint(SERVER_END_POINT).build();
dataRetriever = restAdapter.create(DataRetriever.class);
public void getUserList(final String name) {
Log.i(TAG, "getting user list for: " + name);
Observable<UserList> observable = dataRetriever.getUserList(name);
Log.i(TAG, "subscribe to get userlist");
observable.subscribe(new Action1<UserList>() {
#Override
public void call(UserList userList) {
eventBus.post(new DataRetrieverEvent("UserList", userList));
// save to database
for (User user : userList.getItems()) {
Log.i(TAG, user.getLogin());
try {
dbHelper.create(user);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});
}
And DataRetriever is interface for retrofit. I'm sorry for the naming confusion.
public interface DataRetriever {
#GET("/search/users")
public Observable<UserList> getUserList(#Query("q") String name);
}
Any my Activity,
#Override
public void setItems(final UserList userList) {
runOnUiThread(new Runnable() {
#Override
public void run() {
UserAdapter userAdapter = (UserAdapter)recyclerView.getAdapter();
userAdapter.setUserList(userList);
userAdapter.notifyItemRangeInserted(0, userAdapter.getItemCount());
}
});
}