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
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;
}
}
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))
D/CLICKACTION: Item was clicked for TitleTest003
Click on the Delete image (purple box) for Test003