Question

I made the simple test app based on this example. There is one button which inserts data to database and ListView. Both are in MainActivity. In original code was restartLoader() called only from onResume() but it refreshed the ListView only when the onResume() was executed. I put restartLoader() at the end of displayListView() and now it shows new row in the Listview after i push button. But i don't think that it is correct solution.

 public class MainActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor>{


      private SimpleCursorAdapter dataAdapter;

      @Override
      public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main); 

       displayListView();

       Button add = (Button) findViewById(R.id.add);
          add.setOnClickListener(new View.OnClickListener() {

               public void onClick(View v) {

                   ContentValues values = new ContentValues();
                   values.put(SensorsDb.KEY_TYPE, "wld");
                   values.put(SensorsDb.KEY_TITLE, "Basement Water Detector");
                   values.put(SensorsDb.KEY_SERIAL, "33");
                   values.put(SensorsDb.KEY_VALUE, "NO WATER");

                    getContentResolver().insert(MyContentProvider.CONTENT_URI,values);

                    displayListView();   
               }
              });
      }
      @Override
      protected void onResume() {
       super.onResume();
       //Starts a new or restarts an existing Loader in this manager
       getSupportLoaderManager().restartLoader(0, null, MainActivity.this);
      }

      private void displayListView() {
       // The desired columns to be bound
       String[] columns = new String[] {
        SensorsDb.KEY_TITLE,
        SensorsDb.KEY_VALUE
       };
       // the XML defined views which the data will be bound to
       int[] to = new int[] {
         R.id.sensorTitle,
         R.id.sensorState
       };

       // create an adapter from the SimpleCursorAdapter
       dataAdapter = new SimpleCursorAdapter(this, R.layout.custom_row_view, null, columns, to, 0);
       //Ensures a loader is initialized and active.
       getSupportLoaderManager().initLoader(0, null,  this);
       // get reference to the ListView
       ListView listView = (ListView) findViewById(R.id.sensorList);
       // Assign adapter to ListView
       listView.setAdapter(dataAdapter);

       getSupportLoaderManager().restartLoader(0, null, MainActivity.this);   
      }

      // This is called when a new Loader needs to be created.
      @Override
      public Loader<Cursor> onCreateLoader(int id, Bundle args) {
       String[] projection = {
         SensorsDb.KEY_ROWID,
         SensorsDb.KEY_TYPE,
         SensorsDb.KEY_TITLE,
         SensorsDb.KEY_SERIAL,
         SensorsDb.KEY_VALUE};
       CursorLoader cursorLoader = new CursorLoader(this,
         MyContentProvider.CONTENT_URI, projection, null, null, null);
       return cursorLoader;
      }

      @Override
      public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

             dataAdapter.swapCursor(data);
      }

      @Override
      public void onLoaderReset(Loader<Cursor> loader) {

       dataAdapter.swapCursor(null);
      }

      @Override
      public boolean onCreateOptionsMenu(Menu menu) {
       getMenuInflater().inflate(R.menu.main, menu);
       return true;
      }
     }

There is MyContentProvider class

public class MyContentProvider extends ContentProvider{

 private MyDatabaseHelper dbHelper;

 private static final int ALL_SENSORS = 1;
 private static final int SINGLE_SENSOR = 2;

 // authority is the symbolic name of your provider
 // To avoid conflicts with other providers, you should use
 // Internet domain ownership (in reverse) as the basis of your provider authority.
 private static final String AUTHORITY = "com.example.contproctest.contentprovider";

 // create content URIs from the authority by appending path to database table
 public static final Uri CONTENT_URI =
  Uri.parse("content://" + AUTHORITY + "/sensors");

 // a content URI pattern matches content URIs using wildcard characters:
 // *: Matches a string of any valid characters of any length.
    // #: Matches a string of numeric characters of any length.
 private static final UriMatcher uriMatcher;
 static {
  uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  uriMatcher.addURI(AUTHORITY, "sensors", ALL_SENSORS);
  uriMatcher.addURI(AUTHORITY, "sensors/#", SINGLE_SENSOR);
 }

 // system calls onCreate() when it starts up the provider.
 @Override
 public boolean onCreate() {
  // get access to the database helper
  dbHelper = new MyDatabaseHelper(getContext());
  return false;
 }

 //Return the MIME type corresponding to a content URI
 @Override
 public String getType(Uri uri) {

  switch (uriMatcher.match(uri)) {
  case ALL_SENSORS:
   return "vnd.android.cursor.dir/vnd.com.example.contproctest.contentprovider.sensors";
  case SINGLE_SENSOR:
   return "vnd.android.cursor.item/vnd.com.example.contproctest.contentprovider.sensors";
  default:
   throw new IllegalArgumentException("Unsupported URI: " + uri);
  }
 }

 // The insert() method adds a new row to the appropriate table, using the values
 // in the ContentValues argument. If a column name is not in the ContentValues argument,
 // you may want to provide a default value for it either in your provider code or in
 // your database schema.
 @Override
 public Uri insert(Uri uri, ContentValues values) {

  SQLiteDatabase db = dbHelper.getWritableDatabase();
  switch (uriMatcher.match(uri)) {
  case ALL_SENSORS:
   //do nothing
   break;
  default:
   throw new IllegalArgumentException("Unsupported URI: " + uri);
  }
  long id = db.insert(SensorsDb.SQLITE_TABLE, null, values);
  getContext().getContentResolver().notifyChange(uri, null);
  return Uri.parse(CONTENT_URI + "/" + id);
 }

 // The query() method must return a Cursor object, or if it fails,
 // throw an Exception. If you are using an SQLite database as your data storage,
 // you can simply return the Cursor returned by one of the query() methods of the
 // SQLiteDatabase class. If the query does not match any rows, you should return a
 // Cursor instance whose getCount() method returns 0. You should return null only
 // if an internal error occurred during the query process.
 @Override
 public Cursor query(Uri uri, String[] projection, String selection,
   String[] selectionArgs, String sortOrder) {

  SQLiteDatabase db = dbHelper.getWritableDatabase();
  SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
  queryBuilder.setTables(SensorsDb.SQLITE_TABLE);

  switch (uriMatcher.match(uri)) {
  case ALL_SENSORS:
   //do nothing
   break;
  case SINGLE_SENSOR:
   String id = uri.getPathSegments().get(1);
   queryBuilder.appendWhere(SensorsDb.KEY_ROWID + "=" + id);
   break;
  default:
   throw new IllegalArgumentException("Unsupported URI: " + uri);
  }

  Cursor cursor = queryBuilder.query(db, projection, selection,
    selectionArgs, null, null, sortOrder);
  return cursor;

 }

 // The delete() method deletes rows based on the seletion or if an id is
 // provided then it deleted a single row. The methods returns the numbers
 // of records delete from the database. If you choose not to delete the data
 // physically then just update a flag here.
 @Override
 public int delete(Uri uri, String selection, String[] selectionArgs) {

  SQLiteDatabase db = dbHelper.getWritableDatabase();
  switch (uriMatcher.match(uri)) {
  case ALL_SENSORS:
   //do nothing
   break;
  case SINGLE_SENSOR:
   String id = uri.getPathSegments().get(1);
   selection = SensorsDb.KEY_ROWID + "=" + id
   + (!TextUtils.isEmpty(selection) ?
     " AND (" + selection + ')' : "");
   break;
  default:
   throw new IllegalArgumentException("Unsupported URI: " + uri);
  }
  int deleteCount = db.delete(SensorsDb.SQLITE_TABLE, selection, selectionArgs);
  getContext().getContentResolver().notifyChange(uri, null);
  return deleteCount;
 }

 // The update method() is same as delete() which updates multiple rows
 // based on the selection or a single row if the row id is provided. The
 // update method returns the number of updated rows.
 @Override
 public int update(Uri uri, ContentValues values, String selection,
   String[] selectionArgs) {
  SQLiteDatabase db = dbHelper.getWritableDatabase();
  switch (uriMatcher.match(uri)) {
  case ALL_SENSORS:
   //do nothing
   break;
  case SINGLE_SENSOR:
   String id = uri.getPathSegments().get(1);
   selection = SensorsDb.KEY_ROWID + "=" + id
   + (!TextUtils.isEmpty(selection) ?
     " AND (" + selection + ')' : "");
   break;
  default:
   throw new IllegalArgumentException("Unsupported URI: " + uri);
  }
  int updateCount = db.update(SensorsDb.SQLITE_TABLE, values, selection, selectionArgs);
  getContext().getContentResolver().notifyChange(uri, null);
  return updateCount;
 }

}
Was it helpful?

Solution

You don't have to call restartLoader after insert new data in the CP because CursorLoader can listen your data and update itself automatically when there are changes in the data source (ContentProvider). Try to call cursor.setNotificationUri() before return the Cursor from the query method in your CP.

@Override
public Cursor query(Uri uri, String[] projection, String selection,
 String[] selectionArgs, String sortOrder) {

  SQLiteDatabase db = dbHelper.getWritableDatabase();
  SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
  queryBuilder.setTables(SensorsDb.SQLITE_TABLE);

  switch (uriMatcher.match(uri)) {
  case ALL_SENSORS:
   //do nothing
   break;
  case SINGLE_SENSOR:
   String id = uri.getPathSegments().get(1);
   queryBuilder.appendWhere(SensorsDb.KEY_ROWID + "=" + id);
   break;
  default:
   throw new IllegalArgumentException("Unsupported URI: " + uri);
  }

  Cursor cursor = queryBuilder.query(db, projection, selection,
    selectionArgs, null, null, sortOrder);

  cursor.setNotificationUri(getContext().getContentResolver(), uri);

  return cursor;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top