Android: Refresh ListFragement using ContentProvid

2019-06-03 05:21发布

问题:

I am using a SQLite database and a ContentProvider to fill a ListFragment. The problem is that ListFragment is not getting refreshed after I add a item. The ListFragment is empty. I have to close and reopen the app to show the added item in the list.

I try to update it like this:

     public class RoomListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {

            //adapter using SQLite and ContentProvider to fill ListFragment
            private SimpleCursorAdapter dataAdapter;

            //needed for create room dialog
            private EditText enter_room;
            private static View textEntryView;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            //display ActionBar items
            setHasOptionsMenu(true);

            // TODO: replace with a real list adapter.
            displayListView();
        }
    @Override
        public void onResume() {
          super.onResume();
          //Starts a new or restarts an existing Loader in this manager
          getLoaderManager().restartLoader(0, null, this);
         }

        @Override
        public void onDestroy() {
          super.onDestroy();
         }

         private void displayListView() {


          // The desired columns to be bound
          String[] columns = new String[] {
            Database.KEY_GROUPADDRESS,
            Database.KEY_NAME,
            Database.KEY_DPT
          };

          // the XML defined views which the data will be bound to
          int[] to = new int[] {
            R.id.groupaddress,
            R.id.name,
            R.id.dpt,
          };

          // create an adapter from the SimpleCursorAdapter
          dataAdapter = new SimpleCursorAdapter(
            getActivity(),
            R.layout.device_info,
            null,
            columns,
            to,
            0);

          //set SimpleCursorAdapter to ListFragmentAdapter
          setListAdapter(dataAdapter);

          //Ensures a loader is initialized and active.
          getLoaderManager().initLoader(0, null, this);   
         }

         // This is called when a new Loader needs to be created.
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
             String[] projection = {
                  Database.KEY_ROWID,
                  Database.KEY_GROUPADDRESS,
                  Database.KEY_NAME,
                  Database.KEY_DPT};
             CursorLoader cursorLoader = new CursorLoader(getActivity(),
                     MyContentProvider.CONTENT_URI, projection, null,       null,      null);
             return cursorLoader;
         }

         @Override
         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.)
             dataAdapter.swapCursor(data);
             dataAdapter.notifyDataSetChanged();

                if (isResumed()) {
                    setListShown(true);
                } else {
                    setListShownNoAnimation(true);
             }
         }

         @Override
         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.
             dataAdapter.swapCursor(null);
         }

I add a item with this code:

//Handle OnClick events on ActionBar items
            @Override
            public boolean onOptionsItemSelected(MenuItem item) {
               // handle item selection
               switch (item.getItemId()) {
                  case R.id.menu_add:
                     //Toast.makeText(getActivity(), "Click", Toast.LENGTH_SHORT).show();

                     LayoutInflater factory = LayoutInflater.from(getActivity());

                     //textEntryView is an Layout XML file containing text field to display in alert dialog
                     textEntryView = factory.inflate(R.layout.dialog_add_room, null);       

                     //get the control from the layout      
                     enter_room = (EditText) textEntryView.findViewById(R.id.enter_room);

                     //create Dialog
                     final AlertDialog.Builder alert1 = new AlertDialog.Builder(getActivity());
                     //configure dialog
                     alert1.setTitle("Raum hinzufügen:").setView(textEntryView)
                     .setPositiveButton("Hinzufügen",
                            new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog,
                                int whichButton) {
                            String roomname = enter_room.getText().toString();

                            Log.d("Insert: ", "Inserting ..");

                            ContentValues values = new ContentValues();
                            //TODO Richtige Spalte für Raumname verwenden
                            values.put(Database.KEY_NAME, roomname);

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

                            dataAdapter.notifyDataSetChanged();

                        }
                     }).setNegativeButton("Abbrechen",
                            new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog,
                                int whichButton) {
                            //cancel dialog
                        }
                     });
                     alert1.show();          
                     return true;
                  default:
                     return super.onOptionsItemSelected(item);
               }
            }

My ContentProvider:

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;

public class MyContentProvider extends ContentProvider{
     private MyDatabaseHelper dbHelper;

     private static final int ALL_COUNTRIES = 1;
     private static final int SINGLE_COUNTRY = 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 = "de.mokkapps.fixknxdemo.contentprovider";

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

     // 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, "countries", ALL_COUNTRIES);
      uriMatcher.addURI(AUTHORITY, "countries/#", SINGLE_COUNTRY);
     }

     // 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_COUNTRIES:
       return "vnd.android.cursor.dir/vnd.com.as400samplecode.contentprovider.countries";
      case SINGLE_COUNTRY:
       return "vnd.android.cursor.item/vnd.com.as400samplecode.contentprovider.countries";
      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_COUNTRIES:
       //do nothing
       break;
      default:
       throw new IllegalArgumentException("Unsupported URI: " + uri);
      }
      long id = db.insert(Database.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(Database.SQLITE_TABLE);

      switch (uriMatcher.match(uri)) {
      case ALL_COUNTRIES:
       //do nothing
       break;
      case SINGLE_COUNTRY:
       String id = uri.getPathSegments().get(1);
       queryBuilder.appendWhere(Database.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 selection 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_COUNTRIES:
       //do nothing
       break;
      case SINGLE_COUNTRY:
       String id = uri.getPathSegments().get(1);
       selection = Database.KEY_ROWID + "=" + id
       + (!TextUtils.isEmpty(selection) ?
         " AND (" + selection + ')' : "");
       break;
      default:
       throw new IllegalArgumentException("Unsupported URI: " + uri);
      }
      int deleteCount = db.delete(Database.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_COUNTRIES:
       //do nothing
       break;
      case SINGLE_COUNTRY:
       String id = uri.getPathSegments().get(1);
       selection = Database.KEY_ROWID + "=" + id
       + (!TextUtils.isEmpty(selection) ?
         " AND (" + selection + ')' : "");
       break;
      default:
       throw new IllegalArgumentException("Unsupported URI: " + uri);
      }
      int updateCount = db.update(Database.SQLITE_TABLE, values, selection, selectionArgs);
      getContext().getContentResolver().notifyChange(uri, null);
      return updateCount;
     }
}

回答1:

You should call notifyDataSetChanged(); from within your content providers' insert method and supply the URI as an argument.

At the point in time you are currently calling the notifyDataSetChanged(); method the insert may not have actually happened as the content provider call to insert will be handled asynchronously.

An example could look something like this

@Override
public Uri insert(Uri uri, ContentValues values) {
    SQLiteDatabase sqlDB = mDB.getWritableDatabase();
    int uriType = sURIMatcher.match(uri);
    long id;
    switch (uriType) {
        case TEAMS:
            id = sqlDB.replace(TeamModel.TEAM_TABLE_NAME, null, values);
            break;
        case CARS:
            id = sqlDB.replace(CarModel.CAR_TABLE_NAME, null, values);
            break;
        case TEAM_ERRORS:
            id = sqlDB.replace(TeamErrorModel.TEAMS_ERRORS_TABLE_NAME, null, values);
            String teamId = values.get(TeamErrorModel.COL_TEAM_ID).toString();
            String selection = TeamModel.COL_ID + " = ?";
            String[] selectionArgs = {teamId};
            setErrorFlagTeamModel(sqlDB, true, selection, selectionArgs);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
    }
    getContext().getContentResolver().notifyChange(uri, null, false);
    return Uri.parse(uri + "/" + id);
}

The first argument is the URI passed in to the insert method and will tell ALL adapters listening in on that particular uri to update their data.

The last argument (false) tells a sync adapter to ignore this change. I assume you are not using a sync adapter

All methods in your ContentProvider should call the notifyChange method in a similar way.

You may well find that the insert actually failed. so check that the records are actually being inserted.

UPDATE As per comment below from @zapi

And you need to add cursor.setNotificationUri(contentresolver, uri) inside the query method or the Cursor does not know for which uri notification it has to listen

Since answering your question you have posted your content provider and I can now see that in fact as per the above quote this is in fact your missing link