Search code examples
androidsqlitelistviewandroid-cursoradapter

Updating the SQLite DB with a Button click on listView


I am trying to build a simple stock application, I have a list view on my main activity which has a "Sell" button on every list item I have. The functionality of the Sell button should decrease the quantity of that particular item by updating the row for that item and setting the quantity to quantity-1.

To achieve this, I have found that setting up an on click listener in my custom cursor adapter class was the way to do it. I am using a content provider class for my Database operations. So what I tried to do is, trigger a function which is in my main activity, within the OnClickListener which is in my cursor adapter. Here is some code that would explain more. (please forgive my terrible programming skills, I am fairly new )

My approach does not seem to work for some reason, first click on Sell button does not do anything, and the second one crashes the application with the reason:

  android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method.

p.s. I did not send the context from the adapter to decrease count method, and it was crashing of a null pointer on the getContentResolver().

Update function in my content provider:

private int updateItem (Uri uri, ContentValues values, String selection, String[] selectionArgs){

    if (values.containsKey(InventoryContract.ItemEntry.COLUMN_NAME)){
        String name = values.getAsString(InventoryContract.ItemEntry.COLUMN_NAME);
        if (name == null){
            throw new IllegalArgumentException("Item requires a name");
        }
    }

    // If values size is zero, do not try to update the database.
    if (values.size() == 0){
        return 0;
    }

    // Otherwise, get writeable database to update the data
    SQLiteDatabase database = mDbHelper.getWritableDatabase();

    // Perform the update on the database and get the number of rows affected
    int rowsUpdated = database.update(InventoryContract.ItemEntry.TABLE_NAME, values, selection, selectionArgs);

    // If 1 or more rows were updated, then notify all listeners that the data at the
    // given URI has changed
    if (rowsUpdated != 0) {
        getContext().getContentResolver().notifyChange(uri, null);
    }

    // Return number of rows updated
    return rowsUpdated;
}

The function I have written ( or tried to write ) in my main activity

    public void decreaseCount(Context context, int columnId, int quantity){

    quantity = quantity -1;

    ContentValues values = new ContentValues();
    values.put(InventoryContract.ItemEntry.COLUMN_QUANTITY, quantity);

    Uri updateUri = ContentUris.withAppendedId(InventoryContract.ItemEntry.CONTENT_URI, columnId);

    int rowsAffected = context.getContentResolver().update(updateUri, values,null, null);

}

and lastly, the custom OnClickListener I have added to the button (p.s. the listener is inside the overriden bindView method of the cursor adapter )

sellButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            int columnIdIndex = mCursor.getColumnIndex(InventoryContract.ItemEntry._ID);
            int quantityIndex = mCursor.getColumnIndex(InventoryContract.ItemEntry.COLUMN_QUANTITY);

            CatalogActivity catalogActivity = new CatalogActivity();
            catalogActivity.decreaseCount(context2, Integer.valueOf(mCursor.getString(columnIdIndex)), Integer.valueOf(mCursor.getString(quantityIndex)));
        }
    });

Thank you in advance !


Solution

  • The problem is very trivial. I fixed your codes. First don't create objects out of activities. Try to use boxing and unboxing technic to retrieve your context back. In your InsertCursorAdapter constructor should be like this

    public ItemCursorAdapter(Context context, Cursor c) { super(context, c); this.context = context; }

    Then you need to save your cursor from bindView method.

    Then you need to bind the context object to get your activity object back. All in all, you would have something like this:

     @Override
    public void bindView(View view, final Context context, Cursor cursor) {
        this.mCursor = cursor;
        TextView nameTextView = view.findViewById(R.id.name);
        TextView quantityTextView = view.findViewById(R.id.quantity);
         sellButton = view.findViewById(R.id.sell_button);
        ImageView imageView = view.findViewById(R.id.item_image);
    
        sellButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
    
                int columnIdIndex = mCursor.getColumnIndex(InventoryContract.ItemEntry._ID);
                int quantityIndex = mCursor.getColumnIndex(InventoryContract.ItemEntry.COLUMN_QUANTITY);
                String col= mCursor.getString(columnIdIndex);
                String quan= mCursor.getString(quantityIndex);
                CatalogActivity catalogActivity = (CatalogActivity) context;
                catalogActivity.decreaseCount( Integer.valueOf(col), Integer.valueOf(quan));
            }
        });
    

    Also I changed your decreaseCount arguments. Because this method is in activity class you don't need to pass it anytime you need to decrease the value. getContentResolver() method is a method in super class AppCompatActivity and because it is public, your activity have implemented it already.

     //TODO: Decrease count by one
    public void decreaseCount(int columnId, int quantity){
    
        quantity = quantity -1;
    
        ContentValues values = new ContentValues();
        values.put(InventoryContract.ItemEntry.COLUMN_QUANTITY, quantity);
    
        Uri updateUri = ContentUris.withAppendedId(InventoryContract.ItemEntry.CONTENT_URI, columnId);
    
        int rowsAffected = getContentResolver().update(updateUri, values,null, null);
    
    }