I have finally after several tutorials make my CursorLoader display in my ListView but I have some questions that I hope you guys can help me to solve.
1) my big problem was this:
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
if(uriMatcher.match(uri)==CUSTOMERS){
DB.open();
return DB.leer();
}
else
{
return null;
}
}
as you can see this is from my Content Provider class, my problem was that I dont figure I need to OPEN my DB.
when I put the code for open the DB works for me but then I dont know if I need to close here or it close after doing this:
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1){
Uri uri = Contenido.CONTENT_URI;
return new CursorLoader(this, uri, null, null, null, null);
}
/** A callback method, invoked after the requested content provider returned all the data */
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) {
mAdapter.swapCursor(arg1);
}
#Override
public void onLoaderReset(Loader<Cursor> arg0) {
mAdapter.swapCursor(null);
}
should I put DB.close() on any place of this functions?
Or maybe below this:
getSupportLoaderManager().initLoader(0, null, this);
getSupport is on the OnCreate Bundle. I really don't understand if its necesary close the db that is open from the Cursor query.
Related
I have a class that loads the images on the device. This is an example of my activity:
public class MediaListActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> {
private ProgressDialog progressDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
progressDialog.show();
getSupportLoaderManager().initLoader(1, null, this);
}
#Override
public android.support.v4.content.Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader cursorLoader = new CursorLoader(this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
return cursorLoader;
}
#Override
public void onLoadFinished(android.support.v4.content.Loader<Cursor> loader, Cursor cursor) {
progressDialog.dismiss();
}
}
Of course, I can't put progressDialog.show() inside the onCreate() method, since if the ContentObserver find a change in MediaStore.Images.Media.EXTERNAL_CONTENT_URI and the Cursor is reloaded the method progressDialog.show() will not be called.
How can I solve this problem?
Thank you in advance
Try showing the dialog in onLoaderReset() callback. It is called anytime a loader is restarted, despite the onCreateLoader() that may not be called every time because the loader might already be created.
I need to show some data from server. There are several fragments where items can be displayed, so it is not easy to mantain updates. I decided to cache data in DB and use ContentProvider to access it. But I have a strange issue.
Initially, there is no data in my database. I create CursorLoader and set CursorAdapter as adapter to ListFragment. Then I receive something from server and insert several records. I supposed Loader to be automatically notified about them and reload data. But nothing happens, and ListFragment stays empty.
The other strange thing related. I've wrote some code to add ActionBar menu for this fragment. In this situation it onCreateOptionsMenu is not called (despite of setHasOptionsMenu(true) in onCreate) and menu not added. When DB already has some data, it works normally.
It seems that something broken inside the Fragment, but there is nothing about this in logs. What do you think, what I am doing wrong?
Here is sample fragment, that works like this:
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.Menu;
import android.view.MenuInflater;
public class FeedFragment3 extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
private static final int LOADER_ID = 1;
private SimpleCursorAdapter mAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public void onResume() {
super.onResume();
getActivity().getActionBar().setTitle(R.string.feed_ab_title);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.feed, menu);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_1, null,
new String[] {"title"},
new int[] {android.R.id.text1}
);
setListAdapter(mAdapter);
getActivity().getSupportLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), Consts.getFeedContentUri(), null, null, null, null);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.changeCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.changeCursor(null);
}
}
Here is a part of ContentProvider. I suppose that calling notifyChange in insert and setNotificationUri in query make ContentProvider notified about changes. May be it is not enough?
public class FeedContentProvider extends ContentProvider {
...
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
DPLog.it(TAG, "Query: [%s]", uri);
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables("feed");
qb.setProjectionMap(mProjectionMap);
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, null);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
DPLog.it(TAG, "insert: [%s]", uri);
String tableName = mTableNamesForCode.get(mUriMatcher.match(uri));
mDb.getWritableDatabase().insert(tableName, null, values);
getContext().getContentResolver().notifyChange(uri, null);
return uri;
}
...
}
Opening fragment in Activity:
private void openFeedFragment() {
mFeedFragment = new FeedFragment3();
showRootFragment(mFeedFragment);
updateFeed();
}
private void updateFeed() {
mApi.requestFeed(
new Response.Listener<FeedResponse>() {
#Override
public void onResponse(FeedResponse response) {
Uri uri = PostsContract.getFeedContentUri();
Set<Long> existingIds = getPostIds(uri);
ArrayList<ContentProviderOperation> operations = createMergeOperations();
try {
getContentResolver().applyBatch(PostsContract.AUTHORITY, operations);
} catch (RemoteException e) {
...
} catch (OperationApplicationException e) {
...
}
}
},
null
);
}
Logs when creating Fragment
03-21 19:12:35.041 22005-22344/com.app D/app.ContentProvider﹕ Cursor size: [0] 03-21 19:12:35.061 22005-22005/com.app V/app.checkpoint-checkpoint﹕ com.app.ui.fragment.FeedFragment3.onLoadFinished (FeedFragment3.java:60)
03-21 19:12:36.012 22005-22066/com.app D/app.API﹕ Request 'feed' finished
03-21 19:12:36.092 22005-22066/com.app D/dalvikvm﹕ GC_FOR_ALLOC freed 1417K, 11% free 12805K/14256K, paused 25ms, total 25ms
03-21 19:12:36.122 22005-22005/com.app I/app.ContentProvider﹕ Query: [content://com.app.content.posts/feed] (PostsContentProvider.java:59)
03-21 19:12:36.122 22005-22005/com.app D/app.ContentProvider﹕ Cursor size: [0] (PostsContentProvider.java:78)
03-21 19:12:36.142 22005-22005/com.app I/app.ContentProvider﹕ insert: [content://com.app.content.posts/feed] (PostsContentProvider.java:97)
03-21 19:12:36.262 22005-22005/com.app D/app.ContentProvider﹕ Inserted id: [1] (PostsContentProvider.java:102)
...
03-21 19:12:37.453 22005-22005/com.app D/app.ContentProvider﹕ Inserted id: [21] (PostsContentProvider.java:102)
03-21 19:12:37.453 22005-22348/com.app I/app.ContentProvider﹕ Query: [content://com.app.content.posts/feed] (PostsContentProvider.java:59)
03-21 19:12:37.453 22005-22348/com.app D/app.ContentProvider﹕ Cursor size: [21] (PostsContentProvider.java:78)
03-21 19:12:37.463 22005-22073/com.app I/app.ContentProvider﹕ Query: [content://com.app.content.posts/feed] (PostsContentProvider.java:59)
03-21 19:12:37.463 22005-22073/com.app D/app.ContentProvider﹕ Cursor size: [21] (PostsContentProvider.java:78)
03-21 19:12:37.463 22005-22005/com.app V/app.checkpoint-checkpoint﹕ com.app.ui.fragment.FeedFragment3.onLoadFinished (FeedFragment3.java:60)
in your code :-
use swapCursor(data) instead of changeCursor(data)
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), Consts.getFeedContentUri(), null, null, null, null);
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
try it
for the first problem the error is the constructor of SimpleCursorAdapter that is deprecated and you must say to listen the changes like this:
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_1,
null,
new String[] {"title"},
new int[] {android.R.id.text1},
SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER)
);
for the second you try this that works for me:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
[...]
}
in the end NOT use getActivity().getActionBar() but getSupportActionBar if you are using
import android.support.v4.app.ListFragment;
EDIT:
improve and fix your loader:
#Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
switch (cursorLoader.getId()) {
case LOADER_ID:
mAdapter.swapCursor(cursor);
break;
}
}
Not sure if this should make a difference, but use the fragment's LoaderManager instead of the activity's.
Replace
getActivity().getSupportLoaderManager().initLoader(LOADER_ID, null, this);
with
getLoaderManager().initLoader(LOADER_ID, null, this);
I tried to follow this tutorial to implement CursorLoader for listview in fragment. Everything is fine when scrolling in listview (even fast scroll, it very smooth), except the first time I switch to this fragment, cursor loader load slow a bit. Does any one know how to speed it up? here is my try:
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
String[] projection = { DatabaseHandler.UserTable.id, DatabaseHandler.UserTable.name };
cursorLoader = new CursorLoader(this, DatabaseAccessUtility.CONTENT_URI, projection, null, null, null);
return cursorLoader;
}
public void onLoadFinished(Loader<Cursor> loader,Cursor cursor) {
if(mAdapter!=null && cursor!=null)
mAdapter.swapCursor(cursor);
public void onLoaderReset(Loader<Cursor> arg0) {
if(mAdapter!=null)
mAdapter.swapCursor(null);
}
I am using cursorLoader in my app.
My question is I am still using startmanagingcursor(cursor); method to manage my cursor in a particularactivity. Is this function still needed?
since this method is deprecated, may I know the exact alternative method.
Everyone said LoaderManager with Loader are the solution to my problem.
The real problem is I have implemented LoaderManager and Loader along with these below overridden methods:
#Override
public void onLoaderReset(Loader<Cursor> arg0)
{
adapter.swapCursor(null);
}
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor)
{
adapter.swapCursor(cursor);
}
#Override
public Loader<Cursor> onCreateLoader(int arg1, Bundle instanceState)
{
CursorLoader cursorLoader= new CursorLoader(getApplicationContext(), searchContentUri, null, selection, selecArgs, null);
return cursorLoader;
}
I wanna know that here where the query is being called?
I am facing an issue with Loader.
I have an Activity, which displays list of records retrieved from local DB. When the activity starts, records are automatically loaded via LoaderManager.initLoader() method.
There is also possibility to manually refresh the list via refresh button in ActionBarSherlock. However, after finishing another activity which adds a record to DB, onLoadFinished is not called.
I am using SimpleCursorLoader and here is code snippet from the activity:
#Override
public void onStart() {
...
getSupportLoaderManager().initLoader(0, null, this);
}
#Override
public void onPause() {
...
getSupportLoaderManager().destroyLoader(0);
}
public void refreshRecords() {
getSupportLoaderManager().restartLoader(0, null, this);
}
#Override
public Loader<Cursor> onCreateLoader(int id, final Bundle args) {
Loader<Cursor> l = new SimpleCursorLoader(this) {
#Override
public Cursor loadInBackground() {
return recordDAO.getCursor();
}
};
l.forceLoad();
return l;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
// updateUI
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
The issue is that after finishing the other activity, onLoaderCreate is called, but onLoaderFinished is not called.
after some debugging, I've found that SimpleCursorAdapter.deliverResults() is also called, bud ends up on .. if (isReset()) { ..
Am I missing something? How to force the reload of data?
Thank you in advance
I have finally found the solution to this problem thanks to the discussion on
https://groups.google.com/forum/#!topic/android-developers/DbKL6PVyhLI
public static <T> void initLoader(final int loaderId, final Bundle args, final LoaderCallbacks<T> callbacks,
final LoaderManager loaderManager) {
final Loader<T> loader = loaderManager.getLoader(loaderId);
if (loader != null && loader.isReset()) {
loaderManager.restartLoader(loaderId, args, callbacks);
} else {
loaderManager.initLoader(loaderId, args, callbacks);
}
}
In addition as of support library 28 make sure that you don't call initLoader from within Fragment.onCreate(). As the updated documentation states
You typically initialize a Loader within the activity's onCreate() method, or within the fragment's onActivityCreated() method.
see https://developer.android.com/guide/components/loaders
RaB solution dont work for me
My worked Solution, was always destroy Loader before restart
Loader<Cursor> loader = mLoaderManager.getLoader(mKeyLoader);
if (loader != null)
{
mLoaderManager.destroyLoader(mKeyLoader);
}
mLoaderManager.restartLoader(mKeyLoader, args, this);
In addition to RaB's answer, if you are using a custom Loader, make sure that if you call super if you overwrite deliverResult():
#Override
public void deliverResult(D data) {
super.deliverResult(data); // <--onLoadFinished() will not be called if you don't call this
...
}
fwiw, I had a similar problem from attempting to immediately restart the loader a second time, before the first onLoadFinished was called, resulting in neither being called.
this worked for me:
if( loader == null )
loader = loaderMngr.initLoader(
0, null, myLoaderCallbacks
);
else if( loader.isAbandoned() )
return;
else
loaderMngr.restartLoader(
0, null, myLoaderCallbacks
);
Check the support library.Use this import android.support.v4.app. Don't use android.app.loadermanager.
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
Initialize part
LoaderManager mLoaderManager=getSupportLoaderManager();
LoaderManager.LoaderCallbacks<Cursor> mCursorLoaderCallbacks=new LoaderManager.LoaderCallbacks<Cursor>() {
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle cursor) {
return new CursorLoader(getActivity(), MediaStore.Video.Media.EXTERNAL_CONTENT_URI, COLUMNS_OF_INTEREST, null, null,
MediaStore.Video.Media.DATE_ADDED + " DESC");
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
}
};
mLoaderManager.initLoader(URL_LOADER_EXTERNAL, null, mCursorLoaderCallbacks);