title: Populating ListView with data from SQLite using CursorLoader
body: <p>I made the simple test app based on <a href="http://sunil-android.blogspot.cz/2013/04/android-database-content-provider.html" rel="nofollow">this example</a>. There is one button which inserts data to database and ListView. Both are in MainActivity. In original code was <code>restartLoader()</code> called only from <code>onResume()</code> but it refreshed the ListView only when the <code>onResume()</code> was executed. I put <code>restartLoader()</code> at the end of <code>displayListView()</code> and now it shows new row in the Listview after i push button. But i don't think that it is correct solution.</p>
  
  
  
  <pre class="lang-java prettyprint-override"><code> public class MainActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks&lt;Cursor&gt;{
  
  
        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&lt;Cursor&gt; 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&lt;Cursor&gt; loader, Cursor data) {
  
               dataAdapter.swapCursor(data);
        }
  
        @Override
        public void onLoaderReset(Loader&lt;Cursor&gt; loader) {
  
         dataAdapter.swapCursor(null);
        }
  
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
         getMenuInflater().inflate(R.menu.main, menu);
         return true;
        }
       }
  </code></pre>
  
  <p>There is MyContentProvider class</p>
  
  <pre class="lang-java prettyprint-override"><code>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;
   }
  
  }
  </code></pre>