I have a Navigation Drawer which has an adapter to populate a ListView with some data. I'm using Retrofit to make an API call to get the data, and utilizing the callback methodology. However, I'm getting an error within the adapter when the getCount method is called because the callback isn't done yet. Obviously there's an architecture issue I'm running into here, so I'd love to hear any ideas to improve this.
private class NavigationDrawerAdapter extends BaseAdapter {
private Context _ctx;
private List<Foo> _foo;
public NavigationDrawerAdapter(Context ctx) {
_ctx = ctx;
setupFoo();
}
private void setupFoo() {
SharedPrefsHelper prefs = new SharedPrefsHelper(_ctx);
String userID = prefs.getItem("userID", "");
ApiManager.getFooService(_ctx).getFoo(userID, fooCallback);
}
Callback fooCallback = new Callback<List<Foo>>() {
#Override
public void success(List<Foo> foo, Response response) {
_foo = foo;
}
#Override
public void failure(RetrofitError e) {
Log.e(Constants.TAG, "Could not get foo: " + e.getMessage());
}
};
#Override
public int getCount() {
// This will throw NullPointerException because _foo isn't populated yet
return _foo.size();
}
}
This usually works for me:
#Override
public int getItemCount() {
return _foo == null ? 0 : _foo.size();
}
returns 0 if the list of elements is null, otherwise it returns the size.
This is because we know that when _foo is empty, the size is 0 because there is nothing in your list if it is null.
also:
#Override
public void success(List<Foo> foo, Response response) {
_foo = foo;
notifyDataSetChanged();
}
Any time you make changes to the backing data of an adapter, you must call notifyDataSetChanged on the adapter.
In your setupFoo(), you should initialize _foo like _foo = new ArrayList<Foo>().
Then, when the callback returns data, after replacing "old" foo data with new data in the success method, call notifyDataSetChanged() to let BaseAdapter know there's new day available.
Related
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'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.
Java POJO Object
public class Section {
#ColumnInfo(name="section_id")
public int mSectionId;
#ColumnInfo(name="section_name")
public String mSectionName;
public int getSectionId() {
return mSectionId;
}
public void setSectionId(int mSectionId) {
this.mSectionId = mSectionId;
}
public String getSectionName() {
return mSectionName;
}
public void setSectionName(String mSectionName) {
this.mSectionName = mSectionName;
}
}
My Query method
#Query("SELECT * FROM section")
LiveData<List<Section>> getAllSections();
Accessing DB
final LiveData<List<Section>> sections = mDb.sectionDAO().getAllSections();
On the next line I am checking sections.getValue() which is always giving me null although I have data in the DataBase and later I am getting the value in the onChanged() method.
sections.observe(this, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sections){
}
});
But when I omit LiveData from the query I am getting the data as expected.
Query Method:
#Query("SELECT * FROM section")
List<Section> getAllSections();
Accessing DB:
final List<Section> sections = mDb.sectionDAO().getAllSections();
On the next line I am checking sections.getValue() which is always giving me null although I have data in the DataBase and later I am getting the value in the onChanged() method.
This is normal behavior, because queries that return LiveData, are working asynchronously. The value is null at that moment.
So calling this method
LiveData<List<Section>> getAllSections();
you will get the result later here
sections.observe(this, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sections){
}
});
from documentation:
Room does not allow accessing the database on the main thread unless you called allowMainThreadQueries() on the builder because it might potentially lock the UI for long periods of time. Asynchronous queries (queries that return LiveData or RxJava Flowable) are exempt from this rule since they asynchronously run the query on a background thread when needed.
I solve this problem through this approach
private MediatorLiveData<List<Section>> mSectionLive = new MediatorLiveData<>();
.
.
.
#Override
public LiveData<List<Section>> getAllSections() {
final LiveData<List<Section>> sections = mDb.sectionDAO().getAllSections();
mSectionLive.addSource(sections, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sectionList) {
if(sectionList == null || sectionList.isEmpty()) {
// Fetch data from API
}else{
mSectionLive.removeSource(sections);
mSectionLive.setValue(sectionList);
}
}
});
return mSectionLive;
}
LiveData is an asynchronous query, you get the LiveData object but it might contain no data. You could use an extra method to wait for the data to be filled and then extract the data.
public static <T> T getValue(LiveData<T> liveData) throws InterruptedException {
final Object[] objects = new Object[1];
final CountDownLatch latch = new CountDownLatch(1);
Observer observer = new Observer() {
#Override
public void onChanged(#Nullable Object o) {
objects[0] = o;
latch.countDown();
liveData.removeObserver(this);
}
};
liveData.observeForever(observer);
latch.await(2, TimeUnit.SECONDS);
return (T) objects[0];
}
I resolved the similar issue as follows
Inside your ViewModel class
private LiveData<List<Section>> mSections;
#Override
public LiveData<List<Section>> getAllSections() {
if (mSections == null) {
mSections = mDb.sectionDAO().getAllSections();
}
return mSections;
}
This is all required. Never change the LiveData's instance.
I would suggest creating another query without LiveData if you need to synchronously fetch data from the database in your code.
DAO:
#Query("SELECT COUNT(*) FROM section")
int countAllSections();
ViewModel:
Integer countAllSections() {
return new CountAllSectionsTask().execute().get();
}
private static class CountAllSectionsTask extends AsyncTask<Void, Void, Integer> {
#Override
protected Integer doInBackground(Void... notes) {
return mDb.sectionDAO().countAllSections();
}
}
if sections.getValue() is null I have to call api for data and insert
in into the database
You can handle this at onChange method:
sections.observe(this, new Observer<List<Section>>() {
#Override
public void onChanged(#Nullable List<Section> sections){
if(sections == null || sections.size() == 0) {
// No data in your database, call your api for data
} else {
// One or more items retrieved, no need to call your api for data.
}
}
});
But you should better put this Database/Table initialization logic to a repository class. Check out Google's sample. See DatabaseCreator class.
For anyone that comes across this. If you are calling LiveData.getValue() and you are consistently getting null. It is possible that you forgot to invoke LiveData.observe(). If you forget to do so getValue() will always return null specially with List<> datatypes.
How to mock a method that takes callback object and invoker of this method use it to delegate result to other callback. Here in my scenario I am creating Rx Single from it.
1. RecentDataModel.java
public class RecentDataModel {
public void getRecentData(RecentDataAdapter recentDataAdapter) {
// Get data from database
// Invoke if success
recentDataAdapter.onSuccess()
// Invoke if error
recentDataAdapter.onError()
}
}
2. RecentDataAdapter.java
public class RecentDataAdapter {
onSuccess(List<String> recents);
onError();
}
3. RecentPresenter
public class RecentPresenter {
public Single<List<String>> getRecentsSingle() {
return Single.create(new Single.OnSubscribe<List<String>>() {
#Override
public void call(final SingleSubscriber<? super List<String>> singleSubscriber) {
mRecentDataModel.getRecentData(new RecentDataAdapter() {
#Override
public void onSuccess(List<String> keywordList) {
singleSubscriber.onSuccess(keywordList);
}
});
}
});
}
}
4. RecentTestCase
public class RecentPresenterTest {
#Mock
RecentDataModel recentModel;
#Test
public void testLoadRecent() {
doAnswer(new Answer() {
#Override
public List<String> answer(InvocationOnMock invocation) throws Throwable {
List<String> recents = new ArrayList<>();
recents.add("Recent1");
recents.add("Recent2");
return recents;
}
}).when(recentModel).getRecentData(any(RecentDataAdapter.class));
RecentPresenter recentProvider = new RecentPresenter(null, null, prefModel);
Action1 onNextListener = mock(Action1.class);
recentProvider.getRecentsSingle.subscribe(onNextListener);
ArgumentCaptor<List<String>> listCaptor = ArgumentCaptor.forClass((Class) List.class);
verify(onNextListener, timeout(5000)).call(listCaptor.capture());
assertNotNull(listCaptor.getValue());
assertEquals(listCaptor.getValue().get(0), "Recent1");
}
}
Note:
Not having access to RecentDataModel and hence can't introduce new method like: List<String> getRecentData();
Although method ResentPresenter.getRecentsSingle() is not doing any business logic. But it gets connected with other methods in the class to produce the output. So mocking RecentDataModel is necessary.
Test is failing in following line "verify(onNextListener, timeout(5000)).call(listCaptor.capture());" because while mocking I am providing any(RecentDataAdapter.class) due to which singleSubscriber.onSuccess(keywordList); never gets called.
The issue is that you don't call your callback on your adapter. As the callback is never called, Single.success is never called. So the Single is not notified to the emited list. And then your verify timeout.
To fix the issue, you have to call the onSuccess callback method in your mock :
doAnswer(new Answer() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
List<String> recents = new ArrayList<>();
recents.add("Recent1");
recents.add("Recent2");
invocation.getArguments()[0].onSuccess(recents);
return null;
}
}).when(recentModel).getRecentData(any(RecentDataAdapter.class));
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());
}
});
}