Skip to content
Advertisement

How to automatically update ListView with custom adapter when item is deleted from database

I created a ListView for which I implemented a custom adapter. Additionally I connected it to a SQLite database, which contains the content for the ListView.

I have one ListView with several items. Every item consists of a TextView and an ImageView which functions as a button. When the user clicks the ImageView, I want to delete the item from the database and automatically update the ListView.

Unluckily, I just know how to delete the item from the database. I can’t think of a way to automatically update the ListView in my program, after the item got deleted from the database.

In the version below, I added an onItemClickListener for the ListView in the MainActivity – but it doesn’t function for when the user clicks the ImageView (even though the ImageView is part of the ListView).

MainActivity

public class MainActivity extends AppCompatActivity {
//References to buttons and other controls on the layout
private Button btn_add;
private EditText et_todo;
private Switch sw;
private static ListView lv;
private static DataAdapter todoAdapter;
private DataBaseHelper dbHelper;

/**
 * Initialization Method
 *
 * @param savedInstanceState
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    dbHelper = new DataBaseHelper(MainActivity.this);

    assignVariables();
    registerClick();
    showAllToDos(dbHelper);
}

private void assignVariables() {
    //assign values to variables
    btn_add = (Button) findViewById(R.id.btn_add);
    et_todo = (EditText) findViewById(R.id.et_todo);
    lv = (ListView) findViewById(R.id.lv);
}

public void showAllToDos(DataBaseHelper dbHelper) {
    todoAdapter = new DataAdapter(MainActivity.this, R.layout.list_item, dbHelper.getAllAsList(), dbHelper);
    lv.setAdapter(todoAdapter);
}

private void registerClick() {
    btn_add.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            String toDoTitle = et_todo.getText().toString();

            if(Pattern.matches("s*", toDoTitle)) {
                Toast.makeText(MainActivity.this, "Title is missing", Toast.LENGTH_SHORT).show();
            } else if(dbHelper.existsInDB(new DataModel(toDoTitle))) {
                Toast.makeText(MainActivity.this, "Already added as ToDo", Toast.LENGTH_SHORT).show();
            } else {
                DataModel dModel = new DataModel(toDoTitle);
                dbHelper.addOne(dModel);

                showAllToDos(dbHelper);
            }
            //empty input field
            et_todo.setText("");
        }
    });

    lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
            DataModel clickedItem = (DataModel) adapterView.getItemAtPosition(position);
            dbHelper.deleteOne(clickedItem);

            showAllToDos(dbHelper);
        }
    });
}

}

DataModel

public class DataModel {

//Attributes
private int id;
private String title;

//Constructors
public DataModel(String title) {
    this.title = title;
}

public DataModel() {

}

//toString
@Override
public String toString() {
    return "DataModel{" +
            "id=" + id +
            ", title='" + title + ''' +
            '}';
}

//Getters and Setters
public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getTitle() {
    return title;
}

public void setTitle(String title) {
    this.title = title;
}

}

DataAdapter

public class DataAdapter extends ArrayAdapter<DataModel> {

/**
 * Attributes
 */
private Context mContext;
private int mResource;
private ArrayList<DataModel> mList;
private DataBaseHelper mDbHelper;


public DataAdapter(Context context, int resource, ArrayList<DataModel> list, DataBaseHelper dbHelper) {
    super(context, resource, list);
    mContext = context;
    mResource = resource;
    mList = list;
    mDbHelper = dbHelper;
}

public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    //get the objects information
    String title = getItem(position).getTitle();

    //create object with the information
    DataModel model = new DataModel(title);

    LayoutInflater inflater = LayoutInflater.from(mContext);
    convertView = inflater.inflate(mResource, parent, false);

    //get TextViews
    TextView tvTitle = (TextView) convertView.findViewById(R.id.task);

    //set information to TextViews
    tvTitle.setText(title);

    //delete function
    ImageView delView = (ImageView) convertView.findViewById(R.id.delView);
    delView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mDbHelper.deleteOne(model);
        }
    });
    return convertView;
}

}

DataBaseHelper

public class DataBaseHelper extends SQLiteOpenHelper {

public static final String TODO_TABLE = "TODO_TABLE";
public static final String COLUMN_ID = "ID";
public static final String COLUMN_TODO_TITLE = "TODO_TITLE";
private Context mContext;

public DataBaseHelper(@Nullable Context context) {
    super(context, "todo.db", null, 1);
    mContext = context;
}

/**
 * Is called when the app requests or inputs new data.
 *
 * @param db
 */
@Override
public void onCreate(SQLiteDatabase db) {

    String createTableStatement = "CREATE TABLE " + TODO_TABLE
            + " (" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
            + COLUMN_TODO_TITLE + " TEXT)";

    db.execSQL(createTableStatement);
    //create new Table
}

/**
 * Called whenever the database version number changes.
 * Prevents the previous apps from crashing.
 *
 * @param sqLiteDatabase
 * @param i
 * @param i1
 */
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

}

/**
 * method to add new database entry
 *
 * @param dModel
 * @return
 */
public boolean addOne(DataModel dModel) {
    SQLiteDatabase db = this.getWritableDatabase(); //for insert actions
    ContentValues cv = new ContentValues(); //Content values stores data in pairs

    cv.put(COLUMN_TODO_TITLE, dModel.getTitle());

    long insert = db.insert(TODO_TABLE, null, cv);

    //clean up, close connection to database and cursor
    db.close();
    if(insert == -1) { //if insert is negative number than insert went wrong
        return false;
    } else { //if insert is positive number than insert succeeded
        return true;
    }
}

public boolean deleteOne(DataModel dModel) {
    //if DataModel is found in the database, delete it and return true
    //if it is not found, return false
    SQLiteDatabase db = this.getWritableDatabase();

    String queryString = "DELETE FROM " + TODO_TABLE
            + " WHERE " + COLUMN_TODO_TITLE + " = " + """ + dModel.getTitle() + """;;

    Cursor cursor = db.rawQuery(queryString, null);

    if(cursor.moveToFirst()) {
        return true;
    } else {
        return false;
    }
}

public ArrayList<DataModel> getAllAsList() {
    //create empty list
    ArrayList<DataModel> returnList = new ArrayList<>();
    //get data from the database
    String queryString = "SELECT * FROM " + TODO_TABLE;
    SQLiteDatabase db = this.getReadableDatabase(); //get data from database
    Cursor cursor = db.rawQuery(queryString, null);

    if(cursor.moveToFirst()) { //returns a true if there were items selected
        //loop through results, create new todo objects, put them into return list
        do {
            String todoTitle = cursor.getString(1);

            DataModel newTodo = new DataModel(todoTitle);
            returnList.add(newTodo);

        } while(cursor.moveToNext());
    } else { //returns a false if no items were selected
        //failure, to not add anything to the list
    }

    //clean up, close connection to database and cursor
    cursor.close();
    db.close();

    return returnList;
}

public boolean existsInDB(DataModel dModel) {
    SQLiteDatabase db = this.getWritableDatabase(); //for insert actions
    String queryString = "SELECT * FROM " + TODO_TABLE
            + " WHERE " + COLUMN_TODO_TITLE + " = " + """ + dModel.getTitle() + """;

    Cursor cursor = db.rawQuery(queryString, null);

    if(cursor.moveToFirst()) {
        return true;
    } else {
        return false;
    }
}

}

I appreciate any kind of help or suggestion. Let me know, if you need further explanation.

Nicole

Advertisement

Answer

In the version below, I added an onItemClickListener for the ListView in the MainActivity – but it doesn’t function for when the user clicks the ImageView (even though the ImageView is part of the ListView).

Consider the following based upon your code:-

Working Example

The following code is based upon yours BUT with various changes that does delete and refresh the list when the image (purple box is clicked).

The list_item.xml layout (altered to highlight the items as no image):-

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/teal_200">
    <TextView
        android:id="@+id/task"
        android:layout_width="400dp"
        android:layout_height="match_parent"
        >
    </TextView>
    <ImageView
        android:id="@+id/delView"
        android:layout_width="100dp"
        android:layout_height="20dp"
        android:layout_gravity="center"
        android:background="@color/purple_200"
        >
    </ImageView>
</LinearLayout>

The DataModel class is unchanged.

The DatabaseHelper class has been changed to a) not close the database (this is inefficient as it will apply the WAL changes and then have to subseuqently open the databaset which is relatively resource costly) and b) to close all cursors when done with the and c) utilise the convenience methods :-

public class DataBaseHelper extends SQLiteOpenHelper {

    public static final String TODO_TABLE = "TODO_TABLE";
    public static final String COLUMN_ID = "ID";
    public static final String COLUMN_TODO_TITLE = "TODO_TITLE";
    private Context mContext;

    public DataBaseHelper(@Nullable Context context) {
        super(context, "todo.db", null, 1);
        mContext = context;
    }
    
    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTableStatement = "CREATE TABLE " + TODO_TABLE
                + " (" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
                + COLUMN_TODO_TITLE + " TEXT)";

        db.execSQL(createTableStatement);
    }
    
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
    
    public boolean addOne(DataModel dModel) {
        SQLiteDatabase db = this.getWritableDatabase(); //for insert actions
        ContentValues cv = new ContentValues(); //Content values stores data in pairs
        cv.put(COLUMN_TODO_TITLE, dModel.getTitle());
        return db.insert(TODO_TABLE, null, cv) > -1;
    }

    public boolean deleteOne(DataModel dModel) {
        SQLiteDatabase db = this.getWritableDatabase();
        return db.delete(TODO_TABLE,COLUMN_TODO_TITLE +"=?",new String[]{dModel.getTitle()}) > 0;
    }

    @SuppressLint("Range")
    public ArrayList<DataModel> getAllAsList() {
        //create empty list
        ArrayList<DataModel> returnList = new ArrayList<>();
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.query(TODO_TABLE,null,null,null,null,null,null);
        while (cursor.moveToNext()) {
            returnList.add(new DataModel(cursor.getString(cursor.getColumnIndex(COLUMN_TODO_TITLE))));
        }
        cursor.close();
        return returnList;
    }

    public boolean existsInDB(DataModel dModel) {
        boolean rv = false;
        SQLiteDatabase db = this.getWritableDatabase(); //for insert actions
        Cursor cursor = db.query(TODO_TABLE,null,COLUMN_TODO_TITLE+"=?",new String[]{dModel.getTitle()},null,null,null);
        if(cursor.moveToFirst()) {
            rv = true;
        }
        cursor.close();
        return rv;
    }
}

DataAdapter

public class DataAdapter extends ArrayAdapter<DataModel> {

   /**
    * Attributes
    */
   private Context mContext;
   private int mResource;
   private ArrayList<DataModel> mList;
   private DataBaseHelper mDbHelper;


   public DataAdapter(Context context, int resource, ArrayList<DataModel> list, DataBaseHelper dbHelper) {
      super(context, resource, list);
      mContext = context;
      mResource = resource;
      mList = list;
      mDbHelper = dbHelper;
   }

   public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
      //get the objects information
      String title = getItem(position).getTitle();

      //create object with the information
      DataModel model = new DataModel(title);

      LayoutInflater inflater = LayoutInflater.from(mContext);
      convertView = inflater.inflate(mResource, parent, false);

      //get TextViews
      TextView tvTitle = (TextView) convertView.findViewById(R.id.task);

      //set information to TextViews
      tvTitle.setText(title);

      //delete function
      ImageView delView = (ImageView) convertView.findViewById(R.id.delView);
      delView.setTag(position); //<<<<< ADDDED Sets the tag with the position in the list
      delView.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            Integer position = new Integer(view.getTag().toString()); // gets the position from the tag
            DataModel model = mList.get(position); // gets the respective DataModel from the ArrayList
            Toast.makeText(view.getContext(),"You clicked the Image for Title = " + model.getTitle() + " position " + view.getTag(),Toast.LENGTH_SHORT).show();
            Log.d("CLICKACTION","Image clicked for title = " + model.getTitle() + " position " + view.getTag());
            /* probably faster but the potential for issues if for some reason deletes other than expected
            if (mDbHelper.deleteOne(model)) {
               mList.remove(model);
               notifyDataSetChanged();
            }
             */
            /* Alternative approach - more intensive but the resultant list IS accurate */
            mDbHelper.deleteOne(model);
            ArrayList<DataModel> newlist = mDbHelper.getAllAsList();
            mList.clear();
            for (DataModel dm: newlist) {
               mList.add(dm);
               notifyDataSetChanged();
            }
         }
      });
      return convertView;
   }
}
  • NOTE an important aspect is utilising the tag to tie the clicked view to the Todo/DataModel.
  • NOTE another important factor is the manipulation of the DataAdapter’s instance of the ArrayList and the subsequent notifyDatasetChanged that tells the adapter to rebuild.

and MainActivity :-

public class MainActivity extends AppCompatActivity {
    //References to buttons and other controls on the layout
    private Button btn_add;
    private EditText et_todo;
    private Switch sw;
    private static ListView lv;
    private static DataAdapter todoAdapter;
    private DataBaseHelper dbHelper;

    /**
     * Initialization Method
     *
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        dbHelper = new DataBaseHelper(MainActivity.this);

        assignVariables();
        registerClick();
        showAllToDos(dbHelper); // creates a new instance of the adapter each time it is called
    }


    private void assignVariables() {
        //assign values to variables
        btn_add = (Button) findViewById(R.id.btn_add);
        et_todo = (EditText) findViewById(R.id.et_todo);
        lv = (ListView) findViewById(R.id.lv);
    }

    public void showAllToDos(DataBaseHelper dbHelper) {
        todoAdapter = new DataAdapter(MainActivity.this, R.layout.list_item, dbHelper.getAllAsList(), dbHelper);
        lv.setAdapter(todoAdapter);
    }
    
    private void registerClick() {
        btn_add.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String toDoTitle = et_todo.getText().toString();

                if(Pattern.matches("s*", toDoTitle)) {
                    Toast.makeText(MainActivity.this, "Title is missing", Toast.LENGTH_SHORT).show();
                } else if(dbHelper.existsInDB(new DataModel(toDoTitle))) {
                    Toast.makeText(MainActivity.this, "Already added as ToDo", Toast.LENGTH_SHORT).show();
                } else {
                    DataModel dModel = new DataModel(toDoTitle);
                    dbHelper.addOne(dModel);

                    showAllToDos(dbHelper);
                }
                //empty input field
                et_todo.setText("");
            }
        });


        lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                Toast.makeText(view.getContext(),"Item was clicked for Title = " + ((DataModel) adapterView.getItemAtPosition(position)).getTitle(),Toast.LENGTH_SHORT).show();
                Log.d("CLICKACTION","Item was clicked for Title" + ((DataModel) adapterView.getItemAtPosition(position)).getTitle());
                //DataModel clickedItem = (DataModel) adapterView.getItemAtPosition(position);
                //dbHelper.deleteOne(clickedItem);
                //showAllToDos(dbHelper);
            }
        });
    }
}

*Results

From a new install (empty Database) then the following actions were taken:-

  1. Add Test001 – Test006 so :-

    1. enter image description here
  2. Click on Item Test003 (NOT the purple box (image))

    1. Toast displays as expected.
    2. Log includes D/CLICKACTION: Item was clicked for TitleTest003
  3. Click on the Delete image (purple box) for Test003

    1. Toast displays as expected Test003 position 2
    2. Log includes D/CLICKACTION: Image clicked for title = Test003 position 2
    3. enter image description here
      1. i.e. Test003 removed from the display
    4. Database via App Inspection shows:-
    • enter image description here
      • i.e. no Test003 row in the database
4 People found this is helpful
Advertisement