I'm trying to delete an item from a listview and from a sqlite database. There are several question on this site but I can't find a proper solution. My question is related with the design. I mean: the database access on Android should be done in async way to avoid to stop the main thread. My current listview, indeed, is loaded using an android Loader that it notified about any operation on the db and call onContentChanged(). The problem is how to proceed with the deletion: what is the best strategy? I see two ways:
Delete from adapter, call notifyDataSetChanged() on it and then delete from db with an AsyncTask (or similar). The Loader will be notified. In this case there is maybe a refresh problem because the adapter is updated again due to loader update.
Delete the item using an AsyncTask (or similar) and wait the update using the Loader. In this case the problem is that the UI is not updated when the user delete the item, but it's going to be deleted later (if no error occur) so it can be a bit weird from user point of view.
Both option will work, but different people will pick different way. The second way easier and more recommended as far as you leave all of the work to Loader, developed by Google.
The doc said it will refresh the data automatically and need to work just with callbacks as onLoadFinished and onLoadReset to complete it:
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
And "clean" adapter when Loader was reset:
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
Another words, I don't see any reason to pick first method, when second is making life easier.
Related
I have an activity that in onCreate calls an API and populates a RecyclerView with product names, current stock, and an EditText to insert a value for each product.
You can then hit an add or subtract button at the top of the activity and all products with an inserted value will have their stock updated in the database by the given values via the API.
I do not pass the on screen stock values to the API (as another user may have changed it in the meantime), simply the inserted values, product ids and whether to increment/decrement the database value.
Currently, after I send the update request and it is successful, I call finish() and just reload the activity to get the updated data.
I'm wondering if it would be better to make an API call for the data after the update request and then just update the RecyclerView, it would almost be duplicate code of the onCreate but would prevent reloading the activity.
Does anyone have any insight as to the efficiency of this vs reloading the activity? It seems like reloading would be the more inefficient option but i'm not too sure.
Assume in your onCreate method you have something like this:
onCreate() {
setContentView(xx);
callApiGetDataAndSetDataToRecyclerView();
}
If so, pls try to change the code like below after I send the update request and it is successful:
doAfterUpdateRequestSuccessful() {
List<Object> latestData = callApiGetData() // this should not be in the ui thread, I believe you are clear about it but just want to repeat
recyclerViewAdapter.setData(latestData);
recyclerViewAdapter.notifyDataSetChanged();
}
Then your recycler view will be updated with latest data.
And like #ADM's comment, finish and recreate the activity is never a solution for updating recycler view, the ux will be very very bad.
Reload the activity is not so efficient. You should instead move your fetch data and render logic to an independent method and invoke this method from onCreate and every time you want to refresh the rendered data.
Currently I am implementing MVP pattern for one of my module which has multiple loaders but I am facing problems in updating the Notification from the second loader call
Scenario:
Fragment invokes Presenter.loadMethod() initially to load the data in gridview and once the user clicks on individual item Presenter invokes Presenter.Download() method to download the file(not using download manager)
And I need to update the progress indication like whether the file is in download state or progress state but when the orientation is changed I am not able to update the notification bar when I use InitLoader().
I tried with restartLoader() too but still facing the same problem.
Can anyone please explain what is the best approach to solve this problem?
You need to keep list of items being downloaded, save the list in onSaveInstanceState(). Start a separate loader for every download item.
In onCreate() method, you restore the items being downloaded and reconnect to loaders by calling getLoaderManager().initLoader(itemId, null, this).
In onLoadFinished(), you should check if an item is downloaded successfully and change its state to "downloaded", then remove the item from the list.
I am trying to understand some finer points of AsyncTaskLoaders. This may be obvious, to others but I can't find an unambiguous example or definition that demonstrates and exmplains what happens when you override the deliverResult() method. What actually gets delivered ? How does this interact with the calling object ? I can see use of super.deliverResult, which passes a private object from the class. So, does the loader automatically know what to associate with the "delivered result". I am totally confused.
Seems I'm a bit late to the party, but anyway...
One of the main advantages of this intermediary step between the background loading and the UI thread's callback onLoadFinished() getting called
loadInBackground()
deliverResult() and
the callback onLoadFinished()
is that it gives us a means of shortcutting the whole loading process from within the AsyncTaskLoader class.
And this can be put to good use for caching the loading result within your AsyncTaskLoader and preventing the background loading from happening if there is cached data.
And why would we want to do this? Isn't the whole point of loaders dealing with those dreaded activity lifecycle issues (e.g. rotating the device), maintaining state (like, caching data) and having a means to get updated when underlying data changes (CursorLoader)?
Well, yes, but this isn't the whole story.
Consider this use case:
You've got your app (the one with the AsynTaskLoader) up-and-running and it already has loaded data into your UI.
Then, you switch over to your Twitter app to check on some news and return to you app.
Without caching, upon returning to your app, the loader would do its reloading.
This behavior is different from the one after configuration changes, e.g. rotating your device, in which case no reloading would take place.
So, how would we then prevent the loader from re-fetching data in case we're just sending our app to the background and, later, return to it again?
Solution
Create a cache member variable in your AsyncTaskLoader implementation.
Override deliverResult() so that you save your fetched data in your cache first, before you call the superclass's implementation of deliverResult().
In onStartLoading() check if there's cached data, and if so, let your AsyncTaskLoader just deliver that. Otherwise, start loading.
Here's a link to a sample app which implements this behaviour.
It's just a "Toy app" and as such part of Udacity's current version of the "Developing Android Apps" fundamentals course. And here is the link to the respective video within that course that deals with this issue. (The course is free, but you'll still have to sign-up w/ Udacity).
In short, what this app demonstrates, is a UI in which the user can input a search query for searching GitHub's repos (via the GitHub API), showing the resulting search URL in a TextView and also the raw JSON fetched from GitHub in another TextView.
The whole action happens in just MainActivity.java and the relevant part here is within the AsyncTaskLoader that's implemented as an anonymous inner class:
For step 1, just introduce a member variable in your AsyncTaskLoader implementation that's meant to serve as your data cache.
/* This String will contain the raw JSON
from the results of our Github search */
String mGithubJson;
For step 2, override deliverResult() as to cache the loading result.
When loadInBackground() has finished, it passes its return value to deliverResult().
It does so anyway, but now that we've overridden deliverResult() we can step right in and store our fetched data into the cache member variable which we've created with just so good foresight.
And finally, we chain up to the super class implementation of deliverResult() with super.deliverResult() which will pass-on the result to the callback method onLoadFinished(), running on the UI thread.
#Override
public void deliverResult(String githubJson) {
mGithubJson = githubJson;
super.deliverResult(githubJson);
}
For step 3, check in onStartLoading() whether or not we've got cached data.
If we don't have cached data (yet), just force the loading to begin with a call to forceLoad().
But if we do have cached data, just call deliverResult(yourCachedDataGoesHere) and pass-in the cached data as argument.
if (mGithubJson != null) {
deliverResult(mGithubJson);
} else {
forceLoad();
}
So, if you now switch back and forth between your app and some other app(s), you'll notice that no reloading takes place, as the loader will just use your cached data.
suppose when data are loading in the background, at this time, user press HOME button and exist the app, when user comes back to the app, loading has been finished. So we have already have the data, then AsyncTaskLoader will call the deliverResult() method, deliver the data to the onLoadFinished() method for displaying.
When the user come back to app, onStartLoading() is being called before loadInBackground(). In this method, we could check if our data if empty or not, if not empty, we call deliverResult() and send the result to onLoaderFinished(), so it could prevent to reload data.
When we press HOME exist the app and then come back, it will not create a new Loader, instead the old loader will try to load data.
The only answer I can find that makes any sense is based on a decription in this link.
"A registered listener to receive the Loader's results when it
completes a load. For each of its Loaders, the LoaderManager
registers an OnLoadCompleteListener which will forward the Loader’s
delivered results to the client with a call to
onLoadFinished(Loader loader, D result). Loaders should deliver
results to these registered listeners with a call to
Loader#deliverResult(D result)."
deliverResult appears to be used when you have listeners to the AsyncTask and want to send the results back to them. I would say it's uncommon. The Android documentation is even less descriptive:
"Sends the result of the load to the registered listener. Should only
be called by subclasses. Must be called from the process's main
thread.
Parameters
data : the result of the load"
deliverResult works after doInbackground completes. It sends the result D (returned by doInBackground) to the calling thread. You may wish to override it for cleaning data, but you can do clean-up in doInBackground instead without overriding deliverResult.
I will start with what I am trying to accomplish.
I have a ListFragment, with LoaderCallbacks associated to retrieve data from a DB. The data is downloaded using an AsyncTask, and inserted into the DB. When the user gets to the bottom of the list, using the CWAC-Endless widget the AsyncTask is kicked off and downloads more data.
I am facing a couple of issues here, and I have tried to sort this out over many a night, and I have decided to come here to ask for help.
The first issue is configChanges. When the user rotates the device, the Activity is destroyed, and then recreates all of the Fragments. I know I can use setRetainInstance to true, but this does not help as the AsyncTask is still running when the Activity gets torn down!
The second issue is to do with the Loader. If data is downloaded, and the AsyncTask completes fine, then the items appears in the List fine. Lets say there are 20 items in the DB. When the user rotates the device, and the Fragment is recreated, the Loader needs to be associated again. When this happens, the data is not loaded into the list straight away, and instead the AsyncTask for the download is kicked off because the CWAC-Endless adapter thinks its at the last item in the list!
Both of these issues have exhausted me. I need a fresh look on this, as im getting no where.
Any suggestions will do, and I can post up source code if needed.
EDIT
Ok here are a few more details to help with some suggestions.
I am downloading data from the internet, which will only return a set number of items at a time. I then have to request more data when I want it (pagination).
I decided to use a database, as the new Loader functionality makes it so simple to make sure the data is loaded efficiently and consistant, without any threading issues.
If it would make sense to ditch the Loader approach, and use a standard Adapter to render the data, I am more than happy to ditch this approach and use that. I just wanted to see if someone could offer an insight into why this solution is so difficult.
Thanks,
Adam
When the user gets to the bottom of the list, using the CWAC-Endless widget the AsyncTask is kicked off and downloads more data.
FWIW, I have not tried EndlessAdapter with this combination of stuff (cursors, loaders, and AsyncTask). In particular, quoting the docs:
Note that this has been tested with ArrayAdapter extensively but may not work with other adapter types
I am not even quite certain what the use case would be for an EndlessAdapter applied to a local database. If you have a crazy long list (e.g., thousands of rows), the answer isn't "load it progressively" but "provide a different UX to avoid the long list". For shorter lists, just load the whole thing and be done with it. EndlessAdapter is for cases where the loading is expensive (e.g., Internet access).
That being said, I will add "play with EndlessAdapter and Loader" to my to-do list.
I know I can use setRetainInstance to true, but this does not help as the AsyncTask is still running when the Activity gets torn down!
So? onPostExecute() will not be invoked until the new activity has gotten through onCreate(). Moreover, in a fragment-based model, your task should be talking to the fragment, and if that fragment is retained via setRetainInstance(true), it's the same fragment instance in both the old and the new activity.
When this happens, the data is not loaded into the list straight away
It should be loaded in fairly quickly, though asynchronously. Moreover, I don't see why this is any different from when the activity is created in the first place.
and instead the AsyncTask for the download is kicked off because the CWAC-Endless adapter thinks its at the last item in the list
You should not be creating the EndlessAdapter until after you have data.
I have a simple personal application I'm working on that queries some records in an SQL Database and populates an adapter for a listview and is basically working fine... but I've began to wonder if I'm doing certain things at the right point of the framework.
Currently I'm loading everything up during onCreate(). In theory, I could be loading up quite a bit of data, so I wanted to possibly throw up a ProgressDialog while the information is being added to the adapter, but I ran into some odd threading issues with the Cursor. Ultimately, I launched a Progress Dialog near the end of onCreate(), followed by sleeping on another thread and calling a method to load my data with runOnUiThread() following the short sleep time, having the end of that method dismiss the Progress Dialog.
This works, but it's brought me to whether or not I should be loading database data during onCreate... or whether it should be moved to onStart() or onResume(), adding in code to clear the close and open the database, clear and repopulate the adapter as necessary as other Activity's are started and finished. Or would all that be unnecessary and I should just keep the adapter populated during onCreate()?
Reto Meier's suggestion to use an Application may suit your needs. Take a look at Activity restart on rotation Android
Move it to onResume, as if you stop the activity you can destroy the adapter and fill it back when to resume the activity.
It helps to save memory and also helps to update the adapter if data has changed.