Reputation: 473
When the edit button is clicked on my activity_main.xml it fires this method in my main activity.
public void editActivity(View view) {
editIds.clear();
//Check to see if any activities are ticked (checked) and grab the id if they are.
for (int i = 0; i < adapter.getCount(); i++) {
CheckBox c = listView.getChildAt(i).findViewById(R.id.checkBox);
if (c.isChecked()) {
editIds.add(adapter.getItemId(i));
}
}
//If only one item is checked start the Edit activity
if (editIds.size() == 1) {
Intent intent = new Intent(this, EditActivity.class);
intent.putExtra("ID", String.valueOf(editIds.get(0)));
startActivityForResult(intent, 2);
}
//If more than 1 or none are checked inform the user
else {
String output = "VIEW / EDIT: Please select one activity.";
Toast.makeText(this, output, Toast.LENGTH_SHORT).show();
}
}
This is checking for any checkboxes checked in the ListView. All works fine until the size of the ListView outgrows the size of the screen. What I mean is as soon as the list has to be scrolled clicking Edit causes the app to crash with this error:
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:385)
at android.view.View.performClick(View.java:5610)
at android.view.View$PerformClick.run(View.java:22265)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.View.findViewById(int)' on a null object reference
at com.example.activitytracker.MainActivity.editActivity(MainActivity.java:108)
All works fine until the size of the list exceeds the size of the screen and some research has indicated that although my custom adapter contains all the entries for the entire list view the list view only stores the visible entries. I think this is why I get the error.
How can I populate the ArrayList editIds with the checked items id so I can either retrieve that record from the SQLite db or delete it.
My adapter:
package com.example.activitytracker;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;
public class ActivityAdapter extends CursorAdapter {
public ActivityAdapter(Context context, Cursor cursor) {
super(context, cursor, 0);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.activity, parent, false);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// Find fields to populate in inflated template
TextView activityTitle = (TextView) view.findViewById(R.id.activityTitle);
TextView activityDescription = (TextView) view.findViewById(R.id.activityDescription);
TextView activityDate = (TextView) view.findViewById(R.id.activityDate);
// Extract properties from cursor
String activity = cursor.getString(cursor.getColumnIndexOrThrow("activity"));
String description = cursor.getString(cursor.getColumnIndexOrThrow("description"));
String date = cursor.getString(cursor.getColumnIndexOrThrow("date"));
// Shorten description for cleaner screen display
String displayValueOfDescription = description;
if (description.length() > 20) {
displayValueOfDescription = description.substring(0, 19) + "...";
}
// Create UK date format for screen display
String yearDB = date.substring(0, 4);
String monthDB = date.substring(5, 7);
String dayDB = date.substring(8, 10);
String dateDB = dayDB + "-" + monthDB + "-" + yearDB;
// Populate fields with extracted properties
activityTitle.setText(activity);
activityDescription.setText(String.valueOf(displayValueOfDescription));
activityDate.setText(String.valueOf(dateDB));
}
public void update(Cursor cursor) {
this.swapCursor(cursor);
this.notifyDataSetChanged();
}
}
My layout_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/AppTheme">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/activity"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="18sp"
android:textStyle="italic"
android:padding="5dp"
android:layout_weight="4"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/description"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="18sp"
android:textStyle="italic"
android:padding="5dp"
android:layout_weight="6"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/date"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="18sp"
android:textStyle="italic"
android:padding="5dp"
android:layout_weight="3"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.1">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme">
<Button
android:id="@+id/edit"
android:onClick="editActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/edit" />
<Button
android:id="@+id/delete"
android:onClick="deleteActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/delete" />
</LinearLayout>
</LinearLayout>
My ListView:
<?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="match_parent">
<CheckBox
android:id="@+id/checkBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@null"
android:layout_weight="1"/>
<TextView
android:id="@+id/activityTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/activity"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textSize="15sp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:layout_weight="3"/>
<TextView
android:id="@+id/activityDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/description"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="15sp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:layout_weight="6"/>
<TextView
android:id="@+id/activityDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/date"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="15sp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:layout_weight="3"/>
</LinearLayout>
Any help is appreciated as I am a complete beginner with Android and Java.
Here is my onCreate()
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
dbManager = new DBManager(this);
dbManager.open();
adapter = new ActivityAdapter(this, dbManager.fetch());
listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
}
EDIT
I have added this code to my Adapter
checked.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
// Do the stuff you want for the case when the row TextView is clicked
// you may want to set as the tag for the TextView the position paremeter of the `getView` method and then retrieve it here
Integer realPosition = (Integer) v.getTag();
// using realPosition , now you know the row where this TextView was clicked
Log.d("CLICKEDCHECK", "POS=" + v.getId());
}
});
EDIT Update with code from devgianlu.
Code always returns the same id whatever box you check to edit or delete.
here is the updated ActivityAdapter
package com.example.activitytracker;
import android.content.Context;
import android.database.Cursor;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CursorAdapter;
import android.widget.TextView;
public class ActivityAdapter extends CursorAdapter {
private final SparseBooleanArray checked = new SparseBooleanArray();
public ActivityAdapter(Context context, Cursor cursor) {
super(context, cursor, 0);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.activity, parent, false);
}
@Override
public void bindView(View view, Context context, final Cursor cursor) {
// Find fields to populate in inflated template
TextView activityTitle = (TextView) view.findViewById(R.id.activityTitle);
TextView activityDescription = (TextView) view.findViewById(R.id.activityDescription);
TextView activityDate = (TextView) view.findViewById(R.id.activityDate);
CheckBox check = (CheckBox) view.findViewById(R.id.checkBox);
check.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
checked.put(cursor.getPosition(), isChecked);
}
});
// Extract properties from cursor
String activity = cursor.getString(cursor.getColumnIndexOrThrow("activity"));
String description = cursor.getString(cursor.getColumnIndexOrThrow("description"));
String date = cursor.getString(cursor.getColumnIndexOrThrow("date"));
// Shorten description for cleaner screen display
String displayValueOfDescription = description;
if (description.length() > 20) {
displayValueOfDescription = description.substring(0, 19) + "...";
}
// Create UK date format for screen display
String yearDB = date.substring(0, 4);
String monthDB = date.substring(5, 7);
String dayDB = date.substring(8, 10);
String dateDB = dayDB + "-" + monthDB + "-" + yearDB;
// Populate fields with extracted properties
activityTitle.setText(activity);
activityDescription.setText(String.valueOf(displayValueOfDescription));
activityDate.setText(String.valueOf(dateDB));
}
public boolean isChecked(int pos) {
return checked.get(pos, false);
}
public void update(Cursor cursor) {
this.swapCursor(cursor);
this.notifyDataSetChanged();
}
}
and my call to the isChecked method in MainActivity
public void editActivity(View view) {
editIds.clear();
//Check to see if any activities are ticked (checked) and grab the id if they are.
// Add checked items to deleteIds
for (int i = 0; i < adapter.getCount(); i++) {
if (adapter.isChecked(i)) {
editIds.add(adapter.getItemId(i));
Log.d("EDITACT", "ID="+adapter.getItemId(i) + "size="+editIds.size());
}
}
//If only one item is checked start the Edit activity
if (editIds.size() == 1) {
Intent intent = new Intent(this, EditActivity.class);
intent.putExtra("ID", String.valueOf(editIds.get(0)));
startActivityForResult(intent, 2);
}
//If more than 1 or none are checked inform the user
else {
String output = "VIEW / EDIT: Please select one activity.";
Toast.makeText(this, output, Toast.LENGTH_SHORT).show();
}
}
Further update
My ActivityAdapter now looks like this.
package com.example.activitytracker;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import java.util.ArrayList;
import java.util.List;
class Activities {
private boolean checked = false;
private DBManager dbManager;
Cursor cursor = dbManager.fetch();
public Activities(Cursor cursor) {
List<Activities> items = new ArrayList<>();
while (cursor.moveToNext()){
items.add(new Activities(cursor));
}
}
}
public class ActivityAdapter extends BaseAdapter {
private final List<Activities> items;
private final LayoutInflater inflater;
public ActivityAdapter(Context context, List<Activities> items) {
this.items = items;
this.inflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(R.layout.activity, parent, false);
}
final Activities item = items.get(position);
CheckBox checkBox = convertView.findViewById(R.id.checkBox);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// item.isChecked() = isChecked;
}
});
// *** Bind the data to the view ***
return convertView;
}
public boolean isChecked(int pos) {
// return items.get(pos).checked;
return true;
}
@Override
public int getCount() {
return items.size();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
}
Also now my adapter is not populating my listView on startup and any call to adapter crashes the app.
Here is where I used to check the listview for checked boxes then refresh the adaptor. Not sure what to do here now.
deleteIds.clear();
// Add checked items to deleteIds
for (int i = 0; i < adapter.getCount(); i++) {
CheckBox c = listView.getChildAt(i).findViewById(R.id.checkBox);
if (c.isChecked()) {
deleteIds.add(adapter.getItemId(i));
}
}
//Check to see if any activities are ticked (checked)
if (deleteIds.size() > 0) {
//If there are checked activities a dialog is displayed for user to confirm
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Are you sure you want to delete?")
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
for (int i = 0; i < deleteIds.size(); i++) {
dbManager.delete(deleteIds.get(i));
adapter.update(dbManager.fetch());
}
}
})
.setNegativeButton("No", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
deleteIds.clear();
dialog.cancel();
}
}).show();
}
}
Thanks so much for you help so far.
Upvotes: 0
Views: 84
Reputation: 1580
This is a bit of code I wrote to put all the Cursor data into objects and then bind these objects to views. This way it is much easier to control the checkbox state.
public class CustomItem {
public boolean checked = false;
public CustomItem(Cursor cursor) {
// Get yuor data and put it in the object
}
}
public class CustomAdapter extends BaseAdapter {
private final List<CustomItem> items;
private final LayoutInflater inflater;
public CustomAdapter(Context context, List<CustomItem> items) {
this.items = items;
this.inflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = inflater.inflate(/* Your layout ID */, parent, false);
}
final CustomItem item = items.get(position);
CheckBox checkBox = convertView.findViewById(R.id.checkBox);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
item.checked = isChecked;
}
});
// *** Bind the data to the view ***
return convertView;
}
public boolean isChecked(int pos) {
return items.get(pos).checked;
}
@Override
public int getCount() {
return items.size();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
}
Cursor cursor = ....;
List<CustomItem> items = new ArrayList<>();
while (cursor.moveToNext())
items.add(new CustomItem(cursor));
Adapter adapter = new BaseAdapter(this /* Your activity */, items);
listView.setAdapter(adapter);
for (int i = 0; i < adapter.getCount(); i++) {
System.out.println(i + " IS " + adapter.isChecked(i));
}
Upvotes: 1