Loader not loading data when sqlite data changes

I have a DialogFragment that I'm working on to display two spinners, side by side, one displays a list of drivers, the other a list of vehicles.

The data to populate these spinners is retrieved from a sqlite database. I am trying to use a LoaderManager to keep the spinners updated or in sync with the database tables, (drivers and vehicles).

When I add/delete/edit a record in either the drivers table or vehicles table in the database, the spinners don't get updated, the driver or vehicle remains unchanged in the spinner.

I'm not sure what I'm missing because I thought LoaderManager is supposed to keep the lists updated or in sync with the database tables automatically right?

I created a button called addDriverVehicle() which is supposed to allow the user to add another driver/vehicle in the future but for now I'm using it as a test to delete a driver to kind of simulate the database tables changing just so i can see if the spinner gets updated automatically but it's not happening. The record is being deleted but the spinner continues to show it.

    public class DriverVehiclePickersDialogFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Cursor>, OnItemSelectedListener {

    public static final String ARG_LISTENER_TYPE = "listenerType";
    public static final String ARG_DIALOG_TYPE = "dialogType";
    public static final String ARG_TITLE_RESOURCE = "titleResource";
    public static final String ARG_SET_DRIVER = "setDriver";
    public static final String ARG_SET_VEHICLE = "setVehicle";

    private static final int DRIVERS_LOADER = 0;
    private static final int VEHICLES_LOADER = 1;

    private DriverVehicleDialogListener mListener;

    // These are the Adapter being used to display the driver's and vehicle's data.
    SimpleCursorAdapter mDriversAdapter, mVehiclesAdapter;

    // Define Dialog view
    private View mView;

    // Store Driver and Vehicle Selected
    private long[] mDrivers, mVehicles;

    // Spinners Containing Driver and Vehicle List
    private Spinner driversSpinner;
    private Spinner vehiclesSpinner;

    private static enum ListenerType {
        ACTIVITY, FRAGMENT
    }

    public static enum DialogType {
        DRIVER_SPINNER, VEHICLE_SPINNER, DRIVER_VEHICLE_SPINNER
    }

    public interface DriverVehicleDialogListener {
        public void onDialogPositiveClick(long[] mDrivers, long[] mVehicles);
    }

    public DriverVehiclePickersDialogFragment() {
        // Empty Constructor
        Log.d("default", "default constructor ran");
    }

    public static DriverVehiclePickersDialogFragment newInstance(DriverVehicleDialogListener listener, Bundle dialogSettings) {
        final DriverVehiclePickersDialogFragment instance;

        if (listener instanceof Activity) {
            instance = createInstance(ListenerType.ACTIVITY, dialogSettings);
        } else if (listener instanceof Fragment) {
            instance = createInstance(ListenerType.FRAGMENT, dialogSettings);
            instance.setTargetFragment((Fragment) listener, 0);
        } else {
            throw new IllegalArgumentException(listener.getClass() + " must be either an Activity or a Fragment");
        }

        return instance;
    }

    private static DriverVehiclePickersDialogFragment createInstance(ListenerType listenerType, Bundle dialogSettings) {
        DriverVehiclePickersDialogFragment fragment = new DriverVehiclePickersDialogFragment();

        if (!dialogSettings.containsKey(ARG_LISTENER_TYPE)) {
            dialogSettings.putSerializable(ARG_LISTENER_TYPE, listenerType);
        }

        if (!dialogSettings.containsKey(ARG_DIALOG_TYPE)) {
            dialogSettings.putSerializable(ARG_DIALOG_TYPE, DialogType.DRIVER_VEHICLE_SPINNER);
        }

        if (!dialogSettings.containsKey(ARG_TITLE_RESOURCE)) {
            dialogSettings.putInt(ARG_TITLE_RESOURCE, 0);
        }

        fragment.setArguments(dialogSettings);
        return fragment;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // Find out how to get the DialogListener instance to send the callback events to
        Bundle args = getArguments();
        ListenerType listenerType = (ListenerType) args.getSerializable(ARG_LISTENER_TYPE);

        switch (listenerType) {
        case ACTIVITY: {
            // Send callback events to the hosting activity
            mListener = (DriverVehicleDialogListener) activity;
            break;
        }
        case FRAGMENT: {
            // Send callback events to the "target" fragment
            mListener = (DriverVehicleDialogListener) getTargetFragment();
            break;
        }
        }
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onActivityCreated(savedInstanceState);
        Button btnAddDriverVehicle = (Button) mView.findViewById(R.id.addDriverVehicleButton);

        btnAddDriverVehicle.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                DatabaseHelper1 mOpenHelper = new DatabaseHelper1(getActivity());

                try {
                    SQLiteDatabase db = mOpenHelper.getWritableDatabase();
                    db.delete("drivers", " driver_number = 70", null);
                  } catch (SQLException e) {

                  }
            }
        });         
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);

        Bundle args = getArguments();

        int titleResource = args.getInt(ARG_TITLE_RESOURCE);
        DialogType dialogType = (DialogType) args.getSerializable(ARG_DIALOG_TYPE);

        if (args.containsKey(ARG_SET_DRIVER)) {
            mDrivers = args.getLongArray(ARG_SET_DRIVER);
        }

        if (args.containsKey(ARG_SET_VEHICLE)) {
            mVehicles = args.getLongArray(ARG_SET_VEHICLE);
        }

        mView = LayoutInflater.from(getActivity()).inflate(R.layout.driver_vehicle_dialog, null);

        if ((dialogType == DialogType.DRIVER_SPINNER) || (dialogType == DialogType.DRIVER_VEHICLE_SPINNER)) {
            driversSpinner = (Spinner) mView.findViewById(R.id.driversSpinner);
            vehiclesSpinner = (Spinner) mView.findViewById(R.id.vehiclesSpinner);

            driversSpinner.setVisibility(View.VISIBLE);
            mDriversAdapter = new SimpleCursorAdapter(getActivity(), R.layout.driver_listview_row, null, new String[] { ConsoleContract.Drivers.DRIVER_NUMBER,
                    ConsoleContract.Drivers.DRIVER_NAME }, new int[] { R.id.driver_number, R.id.driver_name }, 0);
            driversSpinner.setAdapter(mDriversAdapter);
            driversSpinner.setOnItemSelectedListener(this);
        }

        if ((dialogType == DialogType.VEHICLE_SPINNER) || (dialogType == DialogType.DRIVER_VEHICLE_SPINNER)) {
            vehiclesSpinner.setVisibility(View.VISIBLE);
            mVehiclesAdapter = new SimpleCursorAdapter(getActivity(), R.layout.vehicle_listview_row, null, new String[] { ConsoleContract.Vehicles.VEHICLE_NUMBER,
                    ConsoleContract.Vehicles.VEHICLE_VIN }, new int[] { R.id.vehicle_number, R.id.vehicle_vin }, 0);
            vehiclesSpinner.setAdapter(mVehiclesAdapter);
            vehiclesSpinner.setOnItemSelectedListener(this);
        }

        // Prepare the loader. Either re-connect with an existing one, or start a new one.
        getLoaderManager().initLoader(DRIVERS_LOADER, null, this);
        getLoaderManager().initLoader(VEHICLES_LOADER, null, this);

        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        builder.setView(mView);

        if (titleResource == 0) {
            builder.setMessage("Select Driver and Vehicle");
        } else {
            builder.setMessage(getString(titleResource));
        }

        builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                mListener.onDialogPositiveClick(mDrivers, mVehicles);
            }
        });

        builder.setNegativeButton(android.R.string.cancel, null);   

        return builder.create();
    }

    private static class DatabaseHelper1 extends SQLiteOpenHelper {
        private static final String DATABASE_NAME = "test.db";
        private static final int DATABASE_VERSION = 1;

        DatabaseHelper1(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    // These are the Contacts rows that we will retrieve.
    static final String[] DRIVERS_SUMMARY_PROJECTION = new String[] { ConsoleContract.Drivers._ID, ConsoleContract.Drivers.DRIVER_ID, ConsoleContract.Drivers.DRIVER_NUMBER,
            ConsoleContract.Drivers.DRIVER_NAME };
    static final String[] VEHICLES_SUMMARY_PROJECTION = new String[] { ConsoleContract.Vehicles._ID, ConsoleContract.Vehicles.VEHICLE_ID, ConsoleContract.Vehicles.VEHICLE_NUMBER,
            ConsoleContract.Vehicles.VEHICLE_VIN };

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created. This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Uri baseUri = null;
        String select = null, sortOrder = null;
        String[] projection = null;

        switch (id) {
        case DRIVERS_LOADER:
            baseUri = ConsoleContract.Drivers.CONTENT_URI;
            select = "((" + Drivers.DRIVER_NAME + " NOT NULL) AND (" + Drivers.DRIVER_NAME + " != '' ))";
            sortOrder = Drivers.DRIVER_NUMBER;
            projection = DRIVERS_SUMMARY_PROJECTION;
            break;
        case VEHICLES_LOADER:
            baseUri = ConsoleContract.Vehicles.CONTENT_URI;
            select = "((" + Vehicles.VEHICLE_NUMBER + " NOT NULL) AND (" + Vehicles.VEHICLE_NUMBER + " != '' ))";
            sortOrder = Vehicles.VEHICLE_NUMBER;
            projection = VEHICLES_SUMMARY_PROJECTION;
            break;
        }

        return new CursorLoader(getActivity(), baseUri, projection, select, null, sortOrder);
    }

    @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.)

        int id = loader.getId();
        MatrixCursor newCursor = null;

        switch (id) {
        case DRIVERS_LOADER:
            newCursor = new MatrixCursor(DRIVERS_SUMMARY_PROJECTION);
            break;
        case VEHICLES_LOADER:
            newCursor = new MatrixCursor(VEHICLES_SUMMARY_PROJECTION);
            break;
        }

        newCursor.addRow(new String[] { "0", "0", "", "" });
        Cursor[] cursors = { newCursor, data };
        Cursor mergedCursor = new MergeCursor(cursors);

        switch (id) {
        case DRIVERS_LOADER:
            mDriversAdapter.swapCursor(mergedCursor);
            break;
        case VEHICLES_LOADER:
            mVehiclesAdapter.swapCursor(mergedCursor);
            break;
        }

    }

    @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.

        int id = loader.getId();

        switch (id) {
        case DRIVERS_LOADER:
            mDriversAdapter.swapCursor(null);
            break;
        case VEHICLES_LOADER:
            mVehiclesAdapter.swapCursor(null);
            break;
        }
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
        if (parent.getId() == R.id.driversSpinner) {
           mDriver = id;
        } else {
           mVehicle = id;
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
    }
}

Answers


Make sure in your ContentProvider to call the notifyChange() method inside the insert, delete and update methods.

Here a snippet taken from Grokking Android Blog

public Uri insert(Uri uri, ContentValues values) { 
   if (URI_MATCHER.match(uri) != LENTITEM_LIST) { 
      throw new IllegalArgumentException("Unsupported URI for insertion: " + uri); 
   } 
   long id = db.insert(DBSchema.TBL_ITEMS, null, values); 
   if (id > 0) { 
      // notify all listeners of changes and return itemUri: 
      Uri itemUri = ContentUris.withAppendedId(uri, id); 
      getContext().getContentResolver().notifyChange(itemUri, null); 
      return itemUri; 
   } 
   // s.th. went wrong: 
   throw new SQLException("Problem while inserting into " + DBSchema.TBL_ITEMS + ", uri: " + uri); // use another exception here!!!
} 

Conversely your Loader won't "heard" DB changes.


Need Your Help

Beyond MySQL docs, what's the best resource for understanding the interworkings of this RdbMS?

mysql documentation

Focused like a hawk on getting the 80% of the core useful functions out of MySQL -- beyond MySQL docs, what's the best resource for understanding the interworkings of this RDBMS?

How do I write my own loop_until?

ruby metaprogramming

I'm practicing my Ruby meta-programming and trying to write my own loop method that will handle most of the ugliness in listening to a socket, but give the programmer the chance to specify the loop...

parametrized query issue in dao in php

php mysql

I am using a phpdao for my database operation.