Im creating a listView and setting a custom arrayAdapter to it in my main activity. Using an onClickListener within the getView method of the adapter to try and remove the item clicked. Its being removed from the array in the adapter but i cant make the listview refresh to show that using notifyDataSetChanged. Ive tried using an onItemClickListener in the main activity instead but i can only get that to work by setting the items in the object view to not clickable which wont work because of the checkboxes. ive tried calling notifyDataSetChanged in the onClickListener in getView, at the end of getView itself but neither do anything. the only way ive gotten the listview to change is creating a new instance of the adapter and setting the listview again which i do in the main activity to add new items, but i dont know if i could do that from the adapter and i cant set onClick for the listview in main activity because of above. i know the data is being removed from the array because the log lines ADAPTER ONCLICK START and END show the removal, but i cannot find a way to update the screen at that moment.
heres the main activity
public class MainActivity extends AppCompatActivity {
CustomAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayList<TaskModel> tasks = read();
//listview
ListView listView = findViewById(R.id.task_list);
adapter = new CustomAdapter(this, tasks);
listView.setAdapter(adapter);
//add button
Button addButton = findViewById(R.id.add_button);
addButton.setOnClickListener(v -> {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
View mView = getLayoutInflater().inflate(R.layout.add_dialog,null);
final EditText txt_inputText = mView.findViewById(R.id.editText);
builder.setView(mView);
builder.setMessage("add a new item").setPositiveButton("Add", (dialog, which) -> {
//do the add here
Log.d("NEW START", String.valueOf(tasks.size()));
tasks.add(new TaskModel(txt_inputText.getText().toString(), false));
write(tasks);
adapter = new CustomAdapter(this, tasks);
listView.setAdapter(adapter);
Toast.makeText(getApplicationContext(),"new item added",Toast.LENGTH_SHORT).show();
Log.d("NEW END", String.valueOf(tasks.size()));
}).setNegativeButton("Cancel", (dialog, which) -> Toast.makeText(getApplicationContext(),"new item cancelled",Toast.LENGTH_SHORT).show());
AlertDialog alert = builder.create();
alert.show();
});
}
}
and the adapter
public class CustomAdapter extends ArrayAdapter{
Context mContext;
ArrayList<TaskModel> data;
public CustomAdapter(Context context, ArrayList<TaskModel> data) {
super(context, R.layout.list_item, data);
this.mContext = context;
this.data = data;
}
public static class ViewHolder{
TextView txtName;
CheckBox chkBox;
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
Log.d("ADAPTER START", String.valueOf(data.size()));
TaskModel model = (TaskModel) getItem(position);
ViewHolder viewHolder;
if(convertView == null){
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.list_item, parent, false);
viewHolder.txtName = convertView.findViewById(R.id.textView);
viewHolder.chkBox = convertView.findViewById(R.id.checkBox);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.txtName.setOnClickListener(v -> {
Log.d("ADAPTER ONCLICK START", String.valueOf(data.size()));
for(int i =0; i < data.size(); i++){
if(viewHolder.txtName.getText().equals(data.get(i).name)){
data.remove(i);
}
}
write(data);
refresh();
Log.d("ADAPTER ONCLICK END", String.valueOf(data.size()));
});
viewHolder.chkBox.setOnCheckedChangeListener((buttonView, isChecked)->{
if(isChecked){
//do checked
}else{
//do unchecked
}
});
viewHolder.txtName.setText(model.name);
viewHolder.chkBox.setChecked(model.done);
return convertView;
}
}
these are the methods for reading and writing data they use bufferedstreams to edit a json array in internalstorage
public class FileController {
public static ArrayList<TaskModel> read(){
ArrayList<TaskModel> tasks = new ArrayList<>();
try{
FileInputStream inputstream = MyApplication.getAppContext().openFileInput("hello.json");
InputStreamReader streamreader = new InputStreamReader(inputstream);
BufferedReader bufferedReader = new BufferedReader(streamreader);
StringBuilder b = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
b.append(line);
}
JSONArray jsonArray = new JSONArray(b.toString());
for(int i=0; i < jsonArray.length(); i++){
tasks.add(new TaskModel(jsonArray.getJSONObject(i)));
}
bufferedReader.close();
}catch (Exception e){
e.printStackTrace();
}
return tasks;
}
public static void write(ArrayList<TaskModel> tasks){
try {
FileOutputStream outputStream = MyApplication.getAppContext().openFileOutput("hello.json", MODE_PRIVATE);
JSONArray jsonArray = new JSONArray();
for(int i =0; i < tasks.size(); i++){
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", tasks.get(i).name);
jsonObject.put("done", tasks.get(i).done);
jsonArray.put(jsonObject);
}
outputStream.write(jsonArray.toString().getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Try changing the structure like this:
Making some quick changes on your code you would have your adapter class like following:
public class CustomAdapter extends ArrayAdapter {
public interface CustomAdapterEventListener {
void onItemDelete(int index);
void onItemCheck(int index, boolean checked);
}
Context mContext;
ArrayList<TaskModel> data;
CustomAdapterEventListener listener;
public CustomAdapter(Context context, ArrayList<TaskModel> data, CustomAdapterEventListener listener) {
super(context, R.layout.list_item, data);
this.mContext = context;
this.data = data;
this.listener = listener;
}
public static class ViewHolder{
TextView txtName;
CheckBox chkBox;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TaskModel model = (TaskModel) getItem(position);
ViewHolder viewHolder;
if(convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.list_item, parent, false);
viewHolder.txtName = convertView.findViewById(R.id.textView);
viewHolder.chkBox = convertView.findViewById(R.id.checkBox);
convertView.setTag(viewHolder);
// You wanna setup the listeners only once when view binding
// Notify the listener (the activity in this case) that an item delete request has been made
viewHolder.txtName.setOnClickListener(v -> listener.onItemDelete(position));
// Notify the listener (the activity in this case) that item check has changed
viewHolder.chkBox.setOnCheckedChangeListener((buttonView, isChecked)->
listener.onItemCheck(position, isChecked));
}
else {
viewHolder = (ViewHolder) convertView.getTag();
}
// Always execute the data filling code
viewHolder.txtName.setText(model.name);
viewHolder.chkBox.setChecked(model.done);
return convertView;
}
public void setDataList(ArrayList<TaskModel> dataList) {
this.data.clear();
this.data.addAll(dataList); // Mind here that we don't change the list's reference
notifyDataSetChanged();
}
}
Then you would implenment the CustomAdapterEventListener
in your activity so that you can process the data upon user interaction with the buttons or checkboxes within list items:
public class MainActivity extends AppCompatActivity implements CustomAdapterEventListener {
private final ArrayList<TaskModel> tasks = new ArrayList<>(); // Make tasks list global
CustomAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tasks.addAll(read()); // Read tasks from file, repo or database etc.
//listview
ListView listView = findViewById(R.id.task_list);
adapter = new CustomAdapter(this, tasks, this); // Added the listener
listView.setAdapter(adapter); // Set adapter once during the activitiy's lifecycle
// unless you need to manage multiple adapters
//add button
Button addButton = findViewById(R.id.add_button);
addButton.setOnClickListener(v -> {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
View mView = getLayoutInflater().inflate(R.layout.add_dialog,null);
final EditText txt_inputText = mView.findViewById(R.id.editText);
builder.setView(mView);
builder.setMessage("add a new item").setPositiveButton("Add", (dialog, which) -> {
//do the add here
Log.d("NEW START", String.valueOf(tasks.size()));
tasks.add(new TaskModel(txt_inputText.getText().toString(), false));
write(tasks);
adapter.setDataList(tasks);
Toast.makeText(getApplicationContext(),"new item added",Toast.LENGTH_SHORT).show();
Log.d("NEW END", String.valueOf(tasks.size()));
}).setNegativeButton("Cancel", (dialog, which) -> Toast.makeText(getApplicationContext(),"new item cancelled",Toast.LENGTH_SHORT).show());
AlertDialog alert = builder.create();
alert.show();
});
}
@Override
public void onItemDelete(int index) {
// Delete the data item here
if(index < tasks.size) {
// Make sure the index valid
tasks.remove(index);
// Update the data in adapter
adapter.setDataList(tasks);
write(tasks); // Save data in file
}
}
@Override
public void onItemCheck(int index, boolean checked) {
// Update the data item's done state here
if(index < tasks.size) {
// Make sure the index valid
tasks.get(index).done = checked;
// Update the data in adapter
adapter.setDataList(tasks);
write(tasks); // Save data in file
}
}
}
Give it a shot, restructure your code as in the above and see how it works.
Note: I didn't tested the code.