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:-
Add Test001 – Test006 so :-
Click on Item Test003 (NOT the purple box (image))
- Toast displays as expected.
- Log includes
D/CLICKACTION: Item was clicked for TitleTest003
Click on the Delete image (purple box) for Test003