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.
Related
I'm making a CommentFragment. I have a RecyclerView to list comments and a Edittext to write comment. However when I add a comment it's sent to server but RecyclerView isn't updated. I use notifydatasetchanged to update. Code:
private void getComments(){
Call<List<CommentsModel>> call=restApiClass.getComments(post_id);
call.enqueue(new Callback<List<CommentsModel>>() {
#Override
public void onResponse(Call<List<CommentsModel>> call, Response<List<CommentsModel>> response) {
if(response.isSuccessful()){
list=response.body();
if(commentsAdapter==null) {
commentsAdapter = new CommentsAdapter(list, getContext());
}
else{
commentsAdapter.notifyDataSetChanged();
}
recyclerView.setAdapter(commentsAdapter);
}
}
#Override
public void onFailure(Call<List<CommentsModel>> call, Throwable t) {
}
});
}
I call this method when I click to sendCommentTextView:
sendCommentTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//............send comment codes.........
getComments();
}
});
check this correct answer: notifyDataSetChanged example.
I think you should follow there the answer to make the adapter work as expected with notifyDataSetChanged(). Shortly: you have to update the list inside the adapter and then notify to it that the list has been updated, so it will render again on ui (recyclerview).
PS: after notifyDataSetChanged function, you don't have to set the adapter again on the recyclerview if already assigned.
Here you updated the list when Retrofit brings new data back, but actually you didn't feed your adapter with this data; you just called commentsAdapter.notifyDataSetChanged();, but you need first to add a method like updateList(List<CommentsModel> list) in your adapter, and call it with updateList(list); before using commentsAdapter.notifyDataSetChanged();
So, add updateList(List<CommentsModel> list) into your adapter to update its list internally like Below:
class CommentsAdapter extends RecyclerView.Adapter<CommentsAdapter.ViewHolder> {
...
List<CommentsModel> mList;
updateList(List<CommentsModel> list) {
this.mList.clear();
this.mList.addAll(list);
}
}
And then the change in your code could be something like:
call.enqueue(new Callback<List<CommentsModel>>() {
#Override
public void onResponse(Call<List<CommentsModel>> call, Response<List<CommentsModel>> response) {
if(response.isSuccessful()){
list=response.body();
if(commentsAdapter==null) {
commentsAdapter = new CommentsAdapter(list, getContext());
}
else{
commentsAdapter.updateList(list);
commentsAdapter.notifyDataSetChanged();
}
recyclerView.setAdapter(commentsAdapter);
}
}
#Override
public void onFailure(Call<List<CommentsModel>> call, Throwable t) {
}
});
Update
Yes it's working but recylerview return to first position and list
reload. But I want to load only last comment. Other comments must stay
same. So how can I do that ?
To just update the last comment in your list, then add a new method into your adapter that takes only that comment, and add it to the list like:
class CommentsAdapter extends RecyclerView.Adapter<CommentsAdapter.ViewHolder> {
...
List<CommentsModel> mList;
addComment(CommentsModel comment) {
this.mList.add(comment);
notifyItemInserted(mList.size()-1);
}
}
Then when the data comes in back by Retrofit:
call.enqueue(new Callback<List<CommentsModel>>() {
#Override
public void onResponse(Call<List<CommentsModel>> call, Response<List<CommentsModel>> response) {
if(response.isSuccessful()){
list = response.body();
if(commentsAdapter == null) {
commentsAdapter = new CommentsAdapter(list, getContext());
}
else{
// updating the adapter list with the last comment
commentsAdapter.addComment((CommentsModel) list.get(list.size()-1));
}
recyclerView.setAdapter(commentsAdapter);
}
}
#Override
public void onFailure(Call<List<CommentsModel>> call, Throwable t) {
}
});
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 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.
I had a retrofit request, when I get data in onResponse,
I did multiples insert in textviews which I called heavy work in the code above, I get the result from OnReponse if there's one, else I get result from database, so the problem I had the same code in OnResponse and OnFailure, so there's any way to put my heavy work outside retrofit, and wait the response to get just one result from OnResponse or OnFailure ??
call.enqueue(new Callback<Dashboard>() {
#Override
public void onResponse(Call<Dashboard> call, Response<Dashboard> response) {
realm.beginTransaction();
dashboard = realm.copyToRealmOrUpdate(response.body());
realm.commitTransaction();
// heavy work : insert in data in multiple text views
}
#Override
public void onFailure(Call<Dashboard> call, Throwable t) {
Log.e("error ", "" + t.getMessage());
dashboard = realm.where(Dashboard.class).findFirst();
// heavy work : insert in data in multiple text views
}
}
Try this..
First Create an interface ..Let's call it OKCallback.
public interface OKCallback {
void onSuccess(String result);
void onFailure(String result);
}
Then in your method that launches the retrofit request, pass final OKCallback okCallback like this..
public void NetworkCall(final OKCallback okCallback){
...........
call.enqueue(new Callback<Dashboard>() {
#Override
public void onResponse(Call<Dashboard> call, Response<Dashboard> response) {
realm.beginTransaction();
dashboard = realm.copyToRealmOrUpdate(response.body());
realm.commitTransaction();
// heavy work : insert in data in multiple text views
okCallback.onSuccess(parcel);
}
Finally simply (ActivityX implements OKCallback) in any class or activity and you should be able to do your heavy work there..You can also wrap your data in the onSuccess methods with a Handler as shown.
#Override
public void onSuccess(String result) {
Handler handler = new Handler(ActivityX.this.getMainLooper());
//process result and
handler.post(new Runnable() {
#Override
public void run() {
//heavy work done here will run on UI thread
}
});
}
You can make an interface and get call back on main thread or after getting response of api call in onSuccess() or in onfailure() start a new AsynTask and process the request in background.
You can change it like this
//create a interface
public interface ConfirmationCallback {
void onSuccess(YourResponseClass value);
void onError();
}
//call this method from your class
yourApiCall(new ConfirmationCallback() {
#Override
public void onSuccess(YourResponseClass value) {
realm.beginTransaction();
dashboard = realm.copyToRealmOrUpdate(value);
realm.commitTransaction();
// heavy work : insert in data in multiple text views
}
#Override
public void onError() {
dashboard = realm.where(Dashboard.class).findFirst();
// heavy work : insert in data in multiple text views
}
});
public void yourApiCall(final ConfirmationCallback confirmationCallback){
call.enqueue(new Callback<Dashboard>() {
#Override
public void onResponse(Call<Dashboard> call, Response<Dashboard> response) {
confirmationCallback.onSuccess(response.body());
}
#Override
public void onFailure(Call<Dashboard> call, Throwable t) {
Log.e("error ", "" + t.getMessage());
confirmationCallback.onError();
}
}
}
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());
}
});
}