I am trying to sort an arraylist in alphabetical order after it has been retrieved from the API call:
StarWarsApi.init();
StarWars api = StarWarsApi.getApi();
m_vwPeopleLayout.setAdapter(m_peopleAdapter);
api.getAllPeople(i, new Callback<SWModelList<People>>() {
#Override
public void success(SWModelList<People> planetSWModelList, Response response) {
for (People p : planetSWModelList.results) {
peopleArrayList.add(p);
}
}
#Override
public void failure(RetrofitError error) {
System.out.print("failure");
}
});
//code to sort arrayList
m_peopleAdapter.notifyDataSetChanged();
The code to sort the list and update the Adapter gets ran, but before the API call is finished. I'm guessing this is caused by the thread not finishing in time. I've tried putting a sleep statement before sorting but it seems that pauses the entire activity.
How can I wait until the API call is finished before running more code?
m_peopleAdapter.notifyDataSetChanged();
should be in your Api-Callback like this
api.getAllPeople(i, new Callback<SWModelList<People>>() {
#Override
public void success(SWModelList<People> planetSWModelList, Response response) {
for (People p : planetSWModelList.results) {
peopleArrayList.add(p);
}
m_peopleAdapter.notifyDataSetChanged();
}
#Override
public void failure(RetrofitError error) {
System.out.print("failure");
}
});
You need to notify the adapter after the dataset was changed. Your code triggered the notification just after the call was started.
How can I wait until the API call is finished before running more
code?
Use success method is called on UI Thread when request is finished. do all Adapter setting or call notifyDataSetChanged inside success method to update Adapter data after getting it from API:
#Override
public void success(....) {
for (People p : planetSWModelList.results) {
peopleArrayList.add(p);
}
// call notifyDataSetChanged here
m_peopleAdapter.notifyDataSetChanged();
}
Related
Apologies in advance if I lack a basic understanding of how to use RxJava2, because this seems to me something that should be quite fundamental. I've wracked my brains with unsuccessful Google searches, so welcome any resource recommendations. I've opted to use a 'sanitized' representation of my workaround code for the sake of clarity.
Problem description
I have an RxJava2 function asyncCallForList() that returns a Maybe<Arraylist<CustomClass>>. Each CustomClass object in this list only has a few basic fields populated (e.g. the source database only contains a unique identifier and a title string for each item).
The full data required for each item is in another database location, which is retrieved using another function asyncCallForItem(uid), which returns a Maybe<CustomClass> based on the unique identifier, where the encapsulated CustomClass has all the required data. This function is to be called for each item in the list returned by asyncCallForList().
The desired functionality is to update my UI once all the objects in the list have been populated.
Workaround #1
It is easy enough to loop through the resulting array list in the doOnSuccess() attached to the initial Maybe<Arraylist<CustomClass>>, then update my UI in the doOnSuccess() on the Maybe<CustomClass> returned by the subsequent asynchronous calls. This is not an acceptable workaround as there will be an unknown number of UI updates being made (the initial list returned could have any amount of items) and will hurt performance.
Workaround #2
This gets the desired outcome but feels like the wrong way to go about it - I suspect there is a more elegant RxJava2 solution. Basically, I create a custom Observable in which loop through the items in the list and get the full data for each. However, rather than update the UI each time I populate a CustomClass item, I increase a counter, then check if the counter exceeds or equals the initial list size. When this condition is met I call the onComplete() method for the observable's emitter and update the UI there.
private void fetchRemoteDataAndUpdateUi() {
//Counter reset to zero before any asynchronous calls are made.
int count = 0;
Maybe<ArrayList<CustomClass>> itemList = asyncCallForList();
Consumer<ArrayList<CustomClass>> onListReturnedSuccess;
onListReturnedSuccess = new Consumer<ArrayList<CustomClass >>() {
#Override
public void accept(ArrayList<CustomClass> list) throws Exception {
//Custom observable created here, in which the resulting array list is processed.
listObservable = Observable.create(new ObservableOnSubscribe<CustomClass>() {
#Override
public void subscribe(final ObservableEmitter<CustomClass> e) throws Exception {
for (CustomClass customClass : list) {
final CustomClass thisCustomClass = customClass;
//Call to get full data on list item called here.
asyncCallForItem(customClass.getUid())
.doOnSuccess(new Consumer<CustomClass>() {
#Override
public void accept(CustomClass customClass) throws Exception {
thisCustomClass.update(customClass);
e.onNext(thisCustomClass);
count++;
if (count >= list.size()) {
e.onComplete();
}
}
}).subscribe();
}
}
});
listObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<CustomClass>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(CustomClass customClass) {
//Here I add the populated CustomClass object to an ArrayList field that is utilised by the UI.
listForUi.add(customClass);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
//Here the UI is finally updated once all CustomClass objects have been populated.
updateUi();
}
});
}
};
//Kick everything off.
itemList.doOnSuccess(onListReturnedSuccess).subscribe();
}
flatMap it!
asyncCallForList()
.subscribeOn(Schedulers.io())
.flatMapSingle(list ->
Flowable.fromIterable(list)
.flatMapMaybe(item ->
asyncCallForItem(item.id)
.subscribeOn(Schedulers.io())
.doOnSuccess(response -> {
// copy state from the original item
response.text = item.text;
})
, 1) // number of concurrent item calls
.toList()
)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(successList -> { /* update UI */ }, error -> { /* report error */ });
I am a newbie in reactive world and trying to implement the following scenario with rxjava/rxandroid 2.x.
I have a local data set as ArrayList mItems in Application class. The same data set is synchronized with server and updated every time user opens the app. However before server returns the response, I want to display the local data set in RecycleView backed by adapter. As soon as the response is returned, the adapter should update the list with delta and without disturbing the order in the UI.
So far I have tried this:
public Observable<List<Item>> getItemsObservable() {
Observable<List<Item>> observeApi = itemServiceAPI.getItemsForUser(accountId);
if (mItems != null) {
return Observable.just(mItems).mergeWith(observeApi);
} else {
return observeApi;
}
}
To update the UI, the above method is invoked like this:
Observable<List<Item>> itemsObservable = appContext.getItemsObservable();
itemsObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver<List<Item>>() {
#Override
public void onNext(List<Item> Items) {
// Code to update the adapter
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
With this I get the onNext called twice for each local data set and remote data set. How to achieve the desired functionality? Does it need use of filter operators to exclude items?
What's the best approach to achieve this?
You can use 'startWith' operator: it subscribes to different observable first.
appContext.getItemsObservable()
.startWith(localCacheObservable)
.subscribe(adapter::updateData)
Adapter's update data should handle diff calculations.
UPDATE
First of all, why are you using Observable.just(mItems) ??? That's unnecessary.
Your code should look like
itemServiceAPI.getItemsForUser(accountId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver<List<Item>>() {
#Override
public void onNext(List<Item> Items) {
// Code to update the adapter
mAdapter.updateItems(items);
/* method in adapter class
*
* public void updateItems(List<Item> mList) {
this.items.addAll(mList);
notifyDataSetChanged();
}
* */
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
Here, your adapter will be updated in onNext. make sure before calling API, you have to set your adapter with local items.
I have over 10 fragments that execute the same kind of task which is :
Retrieving the Data from the server using Retrofit
Starting an Async Task to update the Database (Using ORMLite)
Once the Database is updated, retrieving the new data from the Database
Notify Dataset has changed in the adapter
I'm wondering if it's useless to put the update database code inside an AsyncTask within my fragment once I retrieve the data from the server?
I have trouble understanding what run on the UI thread and what doesn't and should be started as his own thread through an AsyncTask
Here my code:
private void getLocalIncidentTemplate() {
mIncidentTemplate.clear();
mIncidentTemplate.addAll(GenericDAO.getInstance(EntityGroup.class).queryForAll());
Collections.sort(mIncidentTemplate);
Log.e(TAG, "Incident Template count:" + mIncidentTemplate.size());
mListAdapter.notifyDataSetChanged();
spinner.setVisibility(View.GONE);
}
private void getRemoteIncidentTemplate() {
Call<EntityIncident> call = meepServices.getIncidentTemplate();
call.enqueue(new Callback<EntityIncident>() {
#Override
public void onResponse(Call<EntityIncident> call, Response<EntityIncident> response) {
if (response.isSuccessful()) {
new updateIncidentTemplateTask().execute(response.body());
}
}
#Override
public void onFailure(Call<EntityIncident> call, Throwable t) {
t.getStackTrace();
Log.e(TAG, t.toString());
Utils.showToastMessage(getActivity(), "Error retrieving Incidents", true);
}
});
}
private class updateIncidentTemplateTask extends AsyncTask<EntityCategories, Void, Boolean> {
#Override
protected Boolean doInBackground(EntityCategories... params) {
updateIncidents(params[0]);
return true;
}
protected void onPostExecute(Boolean b) {
getLocalIncidentTemplate();
spinner.setVisibility(View.GONE);
}
}
Here is the Database Update Using ORMlite:
private void updateIncident(EntityCategories categories) {
try {
categories.setId("MobilePlan");
//Update base categories
GenericDAO.getInstance(EntityCategories.class).addOrUpdate(categories);
for (EntityCategories.EntityCategory currentCategory : new ArrayList<>(categories.getCategories())) {
if (currentCategory.getmPlans() != null) {
for (EntityPlan myPlan : new ArrayList<>(currentCategory.getmPlans())) {
EntityPlan oldPlan = GenericDAO.getInstance(EntityPlan.class).queryById(String.valueOf(myPlan.getmId()));
myPlan.setCategories(currentCategory);
if (oldPlan != null) {
if (!myPlan.getmDateModification().equals(oldPlan.getmDateModification())) {
GenericDAO.getInstance(EntityPlan.class).addOrUpdate(myPlan);
}
} else {
GenericDAO.getInstance(EntityPlan.class).addOrUpdate(myPlan);
}
}
} else {
continue;
}
GenericDAO.getInstance(EntityLabel.class).addOrUpdate(currentCategory.getmLabel());
currentCategory.setCategories(categories);
GenericDAO.getInstance(EntityCategories.EntityCategory.class).addOrUpdate(currentCategory);
}
} catch (Exception e) {
e.printStackTrace();
}
Log.d(TAG, "DATA updated");
}
For your particular case, you should use the AsyncTask to retrieve data from the backend and place it in the database.
Remember that AsyncTask has three main methods:
onPreExecute() that runs on the UI thread. Useful when you need to prep something that requires UI thread (touching views and whatnot)
doInBackGround() this runs on background thread
onPostExecute() runs also on the UI thread.
In onPostExecute() you could notify your adapter of the new data.
If I were you, I'd use loaders to get notified and retrieve the data off the database. So that the complete chain would be some:
AsyncTask pulls data from the backend and stores it in the database
Your loader will get notified that something changed inside the database and will pull the data from it and call onLoadFinished() method inside your activity/fragment
onLoadFinished() passes the data to the view adapter.
I haven't gone into detail as to how to implement this. I just presented the overall architecture.
I have trouble understanding what run on the UI thread and what doesn't and should be started as his own thread
The short answer:
Everything that might block the UI thread (in other words, might take time) should run on a worker thread (threadpool or dedicated)
DB actions and network requests are classic examples for actions that should always run asynchronously.
In your case I would just use an ORM to wrap all the interaction with the DB, such as ORMlite or any other you find more suitable, in that case you will not have to concern yourself with the inner workings and just provide callbacks for when your calls have finished (successfully or not)
In my Android project i use Azure Mobile Services SDK and the way i make queries to the local sqlite database is like the following:
(example taken from http://azure.microsoft.com/en-us/documentation/articles/mobile-services-android-get-started-data/).
The problem is that i get the following errors:
1)com.microsoft.windowsazure.mobileservices.table.sync.localstore.MobileServiceLocalStoreException: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed
2) A SQLiteConnection object for database 'LocalDatabase' was leaked! Please fix your application to end transactions in progress properly and to close the database when it is no longer needed
3) java.util.concurrent.ExecutionException: com.microsoft.windowsazure.mobileservices.table.sync.localstore.MobileServiceLocalStoreException: java.lang.IllegalStateException: attempt to re-open an already-closed object
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
try {
final MobileServiceList<ToDoItem> result = mToDoTable.where().field("complete").eq(false).execute().get();
runOnUiThread(new Runnable() {
#Override
public void run() {
mAdapter.clear();
for (ToDoItem item : result) {
mAdapter.add(item);
}
}
});
} catch (Exception exception) {
createAndShowDialog(exception, "Error");
}
return null;
}
}.execute();
In this implementation there is no Cursor or SQLiteOpenHelper object to close. What could i do?
Thank you!
I think this is because you are getting the result in one thread but trying to use that same result in the UI thread. One possible method is to use Broadcast or Eventbus to send the data back to the UI once the operation completes:
Here is sort of what I would do:
Futures.addCallback(mToDoTable.where().field("complete").eq(false).execute(), new FutureCallback() {
#Override
public void onSuccess(Object result) {
//Do something with result
//I would use event bus or broadcast here to notify the UI
}
#Override
public void onFailure(Throwable t) {
}
});
If this doesn't work, I would use the debugger and put a break point on that execute().get() line and see what is happening internally.
Here is another possible way to do it:
mToDoTable.where().field("complete").eq(false).execute(new TableQueryCallback<Object>() {
#Override
public void onCompleted(List result, int count, Exception exception, ServiceFilterResponse response) {
}
});
Here is the code:
public void onRefresh() {
MyDDPState.getInstance().getItems(null, new DDPListener() {
#Override
public void onResult(Map<String, Object> json) {
Log.d(TAG, "refreshed");
if (json.get("result") == null) {
/*
* Result is null action
*/
Log.d(TAG, "Null");
swipeLayout.setRefreshing(false);
return;
}
List<Map<String, Object>> temp = (ArrayList) json.get("result");
Log.d(TAG, temp.toString());
MyDDPState.getInstance().initItems(temp);
Log.d(TAG, "converted" + MyDDPState.getInstance().getItems().toString());
Log.d(TAG, swipeLayout.toString());
Log.d(TAG, "Finished refreshing");
swipeLayout.setRefreshing(false);
Log.d(TAG, "Refresh closed");
}
});
}
swipeLayout refers to a private variable in the class that holds the SwipeRefreshLayout. On the callback, I try to call setrefreshing(false) to get rid of the progress indicator, but this call hangs the async function. All the other Logs work except for the "Refresh closed" log.
For some reason, I think because of the library I'm using, errors inside DDP Listeners are not logged either, so I can't trace it. swipeLayout.setRefreshing when called outside of the DDP call work fine.
Anyway, I managed to solve the issue.
When I tried to change a ui variable, the function would stop running.
Turns out the issue was that I was changing UI variables on the wrong thread. To fix it, you have to make the UI calls inside a ui thread by calling:
activity.runOnUiThread(new Runnable() {
#Override
public void run () {
/*
* UI code
*/
}
});
More info here: http://developer.android.com/tools/testing/activity_testing.html