I'm trying to implement a DataListFragment with an adapter that uses a Loader from Commonsware. This Loader uses a SQLiteDatabase directly and doesn't require the use of ContentProviders.
The android reference states about Loaders:
"While Loaders are active they should monitor the source of their data and deliver new results when the contents change."
Under my SQLiteCursor implementation (below), this does not happen. OnLoadFinished() gets called once and that's it. Presumably, one could insert Loader.onContentChanged() calls where the underlying database gets changed, but in general the database code class does not know about loaders, so I'm not sure about the best way to go about implementing this.
Does anyone have any advice on making the Loader "data aware", or should I wrap the database stuff in as a ContentProvider and use CursorLoader instead?
import com.commonsware.cwac.loaderex.SQLiteCursorLoader;
public class DataListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>{
protected DataListAdapter mAdapter; // This is the Adapter being used to display the list's data.
public SQLiteDatabase mSqlDb;
private static final int LOADER_ID = 1;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
int rowlayoutID = getArguments().getInt("rowLayoutID");
// Create an empty adapter we will use to display the loaded data.
// We pass 0 to flags, since the Loader will watch for data changes
mAdapter = new DataListAdapter(getActivity(),rowlayoutID, null , 0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
LoaderManager lm = getLoaderManager();
// OnLoadFinished gets called after this, but never again.
lm.initLoader(LOADER_ID, null, this);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String sql="SELECT * FROM "+TABLE_NAME+";";
String[] params = null;
SQLiteCursorLoader CursorLoader = new SQLiteCursorLoader(getActivity(), mSqlDb, sql, params);
return CursorLoader;
}
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);
// The list should now be shown.
if (isResumed()) { setListShown(true);}
else { setListShownNoAnimation(true); }
}
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);
}
The Loader documentation is flawed.
100% of Loader implementations built into Android itself "monitor the source of their data and deliver new results when the contents change". Since there is only one Loader implementation built into Android itself as of now, their documentation is accurate as far as that goes.
However, quoting a book update of mine that should be released in an hour or two:
There is nothing in the framework that requires this
behavior. Moreover, there are some cases where is clearly a bad idea to do
this – imagine a Loader loading data off of the Internet, needing to
constantly poll some server to look for changes.
I do plan on augmenting SQLiteCursorLoader to be at least a bit more aware of database changes, if you route all database modifications through it. That too will have limitations, because you don't share Loader objects between activities (let alone have access to them from services).
The only reason CursorLoader works as it does is because it uses a ContentProvider -- a singleton that can therefore be aware of all operations.
At the moment, whatever portion of your code is responsible for inserts, updates, and deletes will either need to tap the SQLiteCursorLoader on the shoulder and have it update, or notify the activity of the change (e.g., broadcast from a Service) so the activity can tap the SQLiteCursorLoader on the shoulder.
Related
I'm making android app in which user can work with small images.
App has a backend (parse.com) where images located, so I can add and delete some images.
On the client side images store on SD (files) and there is sqlite table where filepaths located.
App has a Fragment that takes all available images and shows them in GridView with ImageViews. I'm using SQLiteCursorLoader/CursorAdapter to get filepaths and then just load bitmaps to ImageViews.
Also I have IntentService, that sync images with backend by timer. Its load and save to SD new images and remove files of images, that were deleted. Its also modify sqlite table with filepaths of images.
The problem is: IntentService can start doing his job in the same time when user works with Fragment. User can choose image, that has been already deleted by IntentService.
Is there any possibility for IntentService to check that Fragment (or other data consumers) working right now? Should IntentService stop doing his job in that case?
Are there a 'good practices' of solving this kind of issues?
I will be grateful for any help
My preferred way to keep your UI up to date with your database is to implement the content obversable pattern using a Content Provider and observe the changes using the Loader Manager.
Your fragment will look something like this:
public class SomeFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor> {
private CursorAdapter mAdapter;
// ...
#Override public void onViewCreated(View view) {
// ...
mAdapter = new ResourceCursorAdapter(getActivity(), R.layout.my_layout, null, false);
// attach adapter to your grid-view...
getLoaderManager().initLoader(0, null, this);
}
#Override public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), MY_IMAGE_MODEL_URI, null, null, null, null);
}
#Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// this automatically reloads your adapter and updates your grid-view
mAdapter.swapCursor(cursor);
}
#Override public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
}
Note that the Uri that is passed to the CursorLoader will be observed for changes. When a change is detected, the Loader will query the specified Uri after which onLoadFinished is called. But it can only detect changes if you notify the system that a change took place on that Uri. You typically do that inside your content provider, after you have done an update, insert or delete. But it could also be done by your IntentService after it has deleted, inserted or updated your data.
The code to do that will look something like this:
getContentResolver().notifyChange(MY_IMAGE_MODEL_URI, null);
in fragment class Create a public static variable like
public static boolean active = false;
and oAcivityCreated method make it true
onpause or onStop make it false
you can this variable from IntentService
example
if (myfragment.active){
//do Somthing if fragment is running
}else{
//Somthing if not running
}
ithink it will work for you
I have been working on a small To-Do list app. I used CursorLoader to update the ToDolistview from a content provider. I have a written a function onNewItemAdded(), which is called when user enters a new item in the text view and clicks enter. Refer below:
public void onNewItemAdded(String newItem) {
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(ToDoContentProvider.KEY_TASK, newItem);
cr.insert(ToDoContentProvider.CONTENT_URI, values);
// getLoaderManager().restartLoader(0, null, this); // commented for the sake of testing
}
#Override
protected void onResume() {
super.onResume();
//getLoaderManager().restartLoader(0, null, this); // commented for the sake of testing
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader loader = new CursorLoader(this,
ToDoContentProvider.CONTENT_URI, null, null, null, null);
Log.e("GOPAL", "In the onCreateLoader");
return loader;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
int keyTaskIndex = cursor.getColumnIndexOrThrow(ToDoContentProvider.KEY_TASK);
Log.e("GOPAL", "In the onLoadFinished");
todoItems.clear();
if (cursor.moveToNext() == false) Log.e("GOPAL", "Empty Cursor");
else {
while (cursor.moveToNext()) {
ToDoItem newItem = new ToDoItem(cursor.getString(keyTaskIndex));
todoItems.add(newItem);
}
aa.notifyDataSetChanged(); // aa is arrayadapter used for the listview
}
}
I have read, CursorLoader automatically updates the view, whenever there is a data change in the content provider db. That means I suppose, getLoaderManager().restartLoader(0, null, this) has to be called implicitly whenever there is a change in data, right?
But that is not happening. Whenever I add a new item (the item is added to the db from onNewItemAdded, but restartLoader is not explicitly called), pause this activity and resume it back. I don't see any implicit call to restartLoader(even though db is changed) and the listview also is not updated with new item added. Why is that? How does a CursorLoader automatically updates the view even if app is not active???
Thanks :)
EDIT: I have also used getContext().getContentResolver().notifyChange(insertedId, null) in insert of my content provider.
I found the answer for my question. In general, CursorLoader doesn't automatically detect data changes and load them to view. We need to track URI for changes. This can be done by following steps:
Registering an Observer in content resolver through cursor using: (Done in the query method of ContentProvider)
cursor.setNotificationUri(getContext().getContentResolver(), uri);
Now when there is any change in URI underlying data using insert()/delete()/update(), we notify the ContentResolver about the change using:
getContext().getContentResolver().notifyChange(insertedId, null);
This is received by the observer, we registered in step-1 and this calls to ContentResolver.query(), which inturn calls ContentProvider's query() method to return a fresh cursor to LoaderManager. LoaderManager calls onLoadFinished() passing this cursor, along with the CursorLoader where we update the View (using Adapter.swapCursor()) with fresh data.
For Custom AsyncTaskLoaders:
At times we need our custom loader instead of CursorLoader. Here we can use someother object other than cursor to point to the loaded data (like list etc). In this we won't be having previlige to notify ContentResolver through cursor. The application may also not have a content Provider, to track URI changes. In this scenario we use BroadcastReceiver or explicit ContentObserver to achieve automatic view updation. This is as follows:
We need to define our custom loader which extends AsyncTaskLoader and implements all its abstract methods. Unlike CursorLoader, our Custom Loader may or may not use a content Provider and it's constructor may not call to ContentResolver.query(), when this loader is instatiated. So we use a broadcast receiver to serve the purpose.
We need to instantiate a BroadCastReceiver or ContentObserver in OnStartLoading() method of abstract AsyncTaskLoader class.
This BroadCast receiver should be defined to receive data-changing broadcasts from content provider or any system events(Like new app installed) and it has to call loader's onContentChanged() method, to notify the loader about the data change. Loader automatically does the rest to load the updated data and call onLoadFinished() to update the view.
For more details refer this: http://developer.android.com/reference/android/content/AsyncTaskLoader.html
I found this very useful for clear explanation : http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
Well, I think you can restart the loader on certain events. E.g. in my case I have an activity of TODOs. On clicking 'add' option, it launches new activity which has view to feed new TODO.
I am using following code in parent activity's onActivityResult()
getLoaderManager().restartLoader(0, null, this);
It works fine for me. Please share if there is any better approach.
get a reference to your loader while initializing as follows
Loader dayWeatherLoader = getLoaderManager().initLoader(LOADER_DAY_WEATHER, null, this);
then create a class that extends ContentObserver as follows
class DataObserver extends ContentObserver {
public DataObserver(Handler handler) {
super(handler);
}
#Override
public void onChange(boolean selfChange, Uri uri) {
dayWeatherLoader.forceLoad();
}
}
Then register content observer inside onResume lifecycle method as follows
#Override
public void onResume() {
super.onResume();
getContext().getContentResolver().registerContentObserver(CONTENTPROVIDERURI,true,new DayWeatherDataObserver(new Handler()));
}
Whenever there is a change in the underlying data of content provider, the onChange method of contentobserver will be called where you can ask loader to load the data again
I am using a very simple CursorLoader with an ImageDownloader. The ImageDownloader is running and everything but the CursorLoader finishes, and THEN the ImageDownloader begins its job of downloading the images, but the GridView is not updating with the downloaded images..
Within my ListFragment I have the following onActivityCreated method:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.d(TAG, "onActivityCreated");
super.onActivityCreated(savedInstanceState);
cursorAdapter = new OURSVPImageAdapter(getActivity(), null);
// set the adapter on the gridview
mGridView.setAdapter(cursorAdapter);
// load the data
getActivity().getSupportLoaderManager().initLoader(2, null, this);
}
My CursorLoader is as follows:
public static final class PhotoCursorLoader extends SimpleCursorLoader {
Context mContext;
public PhotoCursorLoader(Context context) {
super(context);
Log.d("PhotoCursorLoader", "Constructor");
mContext = context;
}
#Override
public Cursor loadInBackground() {
Log.d("PhotoCursorLoader", "loadInBackground");
PhotosDataSource datasource = new PhotosDataSource(mContext);
# STEP 1
return datasource.getAllPhotos(((EventActivity) mContext).getEventId());
}
}
The line labeled # STEP 1 that retrieves all of the photos is just a method that retrieves a Cursor, as seen here:
public Cursor getAllPhotos(long event_id) {
Log.d(TAG, "getAllPhotos");
Cursor mCursor = getWritableDatabase().query(true, TABLE_NAME, COLUMNS_PHOTOS, DatabaseConstants.KEY_EVENT_ID + "=" + event_id,
null, null, null, null, null);
return mCursor;
}
So, this being the CursorLoader for the ListFragment, it assumes its complete when that Cursor is returned, which is correct. From my understanding the setAdapter() method is what would actually trigger the getView method.
The Trouble I'm having is that Everything seems to be running fine, my log output is outputting the urls for the images correctly, breakpoints all showing legit data, the issue though, is that my grid view never gets updated with the images that the ImageDownloader retrieves.
EDIT
This is the SimpleCursorLoader I'm using: https://gist.github.com/1217628
Once again, the problem is most likely that you are managing your Fragment's loaders from the parent Activity. The Activity and Fragment lifecycles do not sync with each other (at least not in a predictable manner), so your attempts at debugging the issue aren't actually getting you anywhere. The easiest way to guarantee predictable behavior is to have each separate component make use of its own LoaderManager (as opposed to having your Fragments access getActivity().getSupportLoaderManager()).
As with another of my questions.. the refresh shouldn't be forced, and when using a provider you can have the provider notify the contentresovler of updates. I have a method that inserts new photos, and that method uses the data provider. The data provider needed to
getContext().getContentResolver().notifyChange(uri, null);
to actually update the Cursor.
Problem: Activity A is a ListView that contains a ListAdapter, and clicking in any item of the adapter leads to activity B. Activity B has a button that fetches a new item (or several) from the web (using an AsyncTask) and adds it to the list displayed by activity A when pressed. That operation from B is not blocked by a ProgressDialog, so the user can move back to A before the AsyncTask that B started finishes fetching the data.
So I need a way of updating the adapter of A from B.
I have a class C with static data displayed in the ListView by A. When the button at B is pressed, it adds that value to C. That class also has the adapter from A as a static field, but I think that this leaks the memory from the Context, and that is bad. My first idea of fixing this was removing the static adapter from C and every time A onResume() (and if the data on the adapter is different from what I have at C), I load the data from C again into the adapter and notifyDatasetChanged(). Well, it works most of the time, but if the user goes back to A from B before B fetches the data from the web, then the adapter does not update, since the onResume() came before the data is fetched.
Question: Is there a better way of updating the adapter of A from B?
Don't save static references to the adapter. It will indeed leak memory and behave badly.
It appears I misunderstood the first time. Here is an updated answer:
First solution
The prettiest solution is to implement a content provider for the data storage and query that content provider in both A and B. Update the data in B using contentProvider.insert()
and read the data using contentProvider.query() returning for example a SQLiteCursor if it is backed by database or a MatrixCursor if you just save it in memory in the content provider.
The basic steps (without CursorLoader):
In onCreate of A you register yourself as a contentobserver using ContentResolver.registerContentObserver(uri, true, this) where uri is an URI using some scheme you set.
In onCreate of A you get the data by querying the contentprovider using ContentResolver.query(uri, projection, selection, selectionArgs, sortOrder) where projection, selection, selectionArgs and sortOrder can be what fits your contentprovider (maybe null). Uri refers to the data you want to query and is your choice.
When data is loaded in B you call ContentResolver.insert(). In insert of your contentprovider you call ContentResolver.notifyChange (Uri uri, null, null) where uri is the URI you used in step 1.
Implement onChange(boolean selfChange) in A and requery the content provider when it is called.
Note that you will not need to call registerContentObserver at all if you use CursorLoaders!
It will receive the new cursor in the loader since it has automatic requery when you notify change.
Second solution
A less pretty solution is to implement a singleton object that handles the data.
Something like:
Implement the class
public class DataHolder {
private static DataHolder sDataHolder;
private String data = ""; // Data represented by string.
public DataHolder getInstance() {
if (sDataHolder == null) {
sDataHolder = new DataHolder()
}
return sDataHolder;
}
private DataHolder() {} // Hidden constructor
public void setData(final String data) {
mData = data;
for (DataListener listener: mDataListeners) {
listener.onDataChanged(mData);
}
}
public void registerListener(DataListener listener) {
mDataListeners.add(listener);
}
public String unregisterListener(DataListener listener) {
mDataListeners.remove(listener);
}
public String getData() {
return mData;
}
public static interface DataListener {
public void onDataChanged(String data);
}
}
Make A implement DataListener
Read and update the data in onStart() of DataListener to make sure that it is set if the change was done when B was alive using DataHolder.getInstance().getData().
Register listener in A's onCreate/onStart using DataHolder.getInstance().registerListener(this); Let the listener update the data.
Unregister listener in A's onDestroy/onStop using DataHolder.getInstance().unregisterListener(this)
Set the data and signal any listener in B using DataHolder.getInstance().setData(data)
Also note that you can make the second solution fully thread safe by changing void registerListener() to synchronized String registerListenerAndGetValue() if you also make setValue synchronized.
Old answer based on a misunderstanding
My old answer for general result handling did not quite answer the question, but it was:
If you want to send data back to an activity A you should do the following:
Always start B with startActivityForResult (Intent intent, int requestCode)
Set the result when done in B using setResult (int resultCode)
Handle the result when you come back to A by implementing onActivityResult (int requestCode, int resultCode, Intent data)
As an addition you could add so you only fetch data in A the first time by doing it only if savedInstanceState == null in for example onCreate().
try calling mListView.invalidate();
**java-doc
Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().
I have an Android ListActivity that is backed by a database Cursor through a SimpleCursorAdapter.
When the items are clicked, a flag field in the coresponding row in the database is toggled and the view in the list needs to be updated.
The problem is, when the view that's updated goes off screen and is recycled, the old value is displayed on the view when it returns into view. The same thing happens whenever thr list is redrawb (orientation changes, etc).
I use notifydatasetchanged() to refresh the cursor adapter but it seems ineffective.
How should I be updating the database so the cursor is updated as well?
Call requery() on the Cursor when you change data in the database that you want reflected in that Cursor (or things the Cursor populates, like a ListView via a CursorAdapter).
A Cursor is akin to an ODBC client-side cursor -- it holds all of the data represented by the query result. Hence, just because you change the data in the database, the Cursor will not know about those changes unless you refresh it via requery().
UPDATE: This whole question and set of answers should be deleted due to old age, but that's apparently impossible. Anyone seeking Android answers should bear in mind that the Android is a swiftly-moving target, and answers from 2009 are typically worse than are newer answers.
The current solution is to obtain a fresh Cursor and use either changeCursor() or swapCursor() on the CursorAdapter to affect a data change.
requery is now deprecated. from the documentation:
This method is deprecated.
Don't use this. Just request a new cursor, so you can do this asynchronously and update your list view once the new cursor comes back.
after obtaining a new cursor one can use theadapter.changeCursor(cursor). this should update the view.
In case of using loader and automagically generated cursor you can call:
getLoaderManager().restartLoader(0, null, this);
in your activity, just after changing something on a DB, to regenerate new cursor.
Don't forget to also have event handlers defined:
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader cursorLoader =
new CursorLoader(this,
YOUR_URI,
YOUR_PROJECTION, null, null, null);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
I am not clear if you set the autoRequery property of CursorAdapter to true.
The adapter will check the autoRequery property; if it is false, then the cursor will not be changed.
requery() is already deprecated, just implement the simple updateUI() method like this in your CursorAdapter's child class and call it after data updates:
private void updateUI(){
swapCursor(dbHelper.getCursor());
notifyDataSetChanged();
}
It's easy.
private Db mDbAdapter;
private Cursor mCursor;
private SimpleCursorAdapter mCursorAd;
.....................................
//After removing the item from the DB, use this
.....................................
mCursor = mDbAdapter.getAllItems();
mCursorAd.swapCursor(mCursor);
Or use CursorLoader...