I'm trying to implement a way to delete multiple items from a ListView simultaneously. I found an online tutorial that seems to be working well and implemented it in my app.
The code from the tutorial allows us to select multiple items from the ListView once we press on one of the times for a few seconds, making a small menu popup with a delete button. We can select multiple items and then press delete to remove them from the list.
Everything seems to be working fine when I tested it, except for one thing, I keep getting an null pointer exception when I press on the delete button after having selected one or more items for deletion.
Here is the tutorial I am following: Android Delete Multiple Selected Items in ListView Tutorial
Here is my activity code:
public class Favoris extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener{
/**fields*/
private ListView produitFavorisListView;
private List<StackProduits> listProduits = new ArrayList<>();
private ProduitsAdapter adapterFavoris;
//SharedPreferences mPrefs;
/**
* Method used to initialize the current activity, by inflating the activity's UI and interacting with
* implemented widgets in the UI. Used to get and set the toolbar, (a floating action button), the
* drawer which is used as the side menu, a navigation view.
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_favoris);
/**get ListView reference*/
produitFavorisListView = (ListView) findViewById(R.id.favoritesList);
/**getting back data from shared preferences*/
final SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
final Gson gson = new Gson();
//getting back favorites
Set<String> myJson = mPrefs.getStringSet("listJson2", new HashSet<String>());
//adapterFavoris = new ProduitsAdapter(getApplicationContext(), 0, listProduits);
if (myJson.isEmpty() && listProduits.isEmpty()) {
produitFavorisListView.setAdapter(null);
//Log.i("INFO", "No items"); todo - log info: no items
}
else if (myJson.isEmpty() && listProduits != null) {
adapterFavoris.notifyDataSetChanged();
adapterFavoris = new ProduitsAdapter(getApplicationContext(), -1, listProduits);
produitFavorisListView.setAdapter(adapterFavoris);
}
else{
//for each where we get back values from sting set, then convert to product
for (String id : myJson) {
StackProduits savedProduct = gson.fromJson(id, StackProduits.class);
//savedProduct.setIsAddedAsFav("1");
listProduits.add(savedProduct);
}
adapterFavoris = new ProduitsAdapter(getApplicationContext(), -1, listProduits);
produitFavorisListView.setAdapter(adapterFavoris);
}
//Set the click listener to launch the browser when a row is clicked.
produitFavorisListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int pos, long id) {
Intent intentProduitFavorisDetail = new Intent(Favoris.this, ProduitDetail.class);
StackProduits ProduitFavoris = ProduitsXmlPullParser.getStackProduitFromFile(Favoris.this).get(pos);
intentProduitFavorisDetail.putExtra("produit", ProduitFavoris);
startActivity(intentProduitFavorisDetail);
}
});
/**handle multiple item selection for deletion*/
produitFavorisListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
produitFavorisListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener(){
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.favorite_menu, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false; //done
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()){
case R.id.delete_fav:
// Calls getSelectedIds method from ListViewAdapter Class
SparseBooleanArray selected = adapterFavoris.getSelectedIds();
// Captures all selected ids with a loop
for (int i = (selected.size() - 1); i >= 0; i--) {
if (selected.valueAt(i)) {
StackProduits selecteditem = adapterFavoris.getItem(selected.keyAt(i));
// Remove selected items following the ids
adapterFavoris.remove(selecteditem);
adapterFavoris.notifyDataSetChanged();
/*if(listProduits.isEmpty()){
if()
}*/
}
}
//save after modifications
String getProduct;
Set<String> stringListProductSave = new HashSet<>();
Gson gsonSave = new Gson();
for(int i=0; i<listProduits.size();i++){
getProduct = gsonSave.toJson(listProduits.get(i));
stringListProductSave.add(getProduct);
}
SharedPreferences.Editor prefsEditorSave = mPrefs.edit();
prefsEditorSave.putStringSet("listJson2", stringListProductSave);
prefsEditorSave.apply();
adapterFavoris.notifyDataSetChanged();
// Close CAB
mode.finish();
return true;
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode mode) {
adapterFavoris.removeSelection();
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
// Capture total checked items
final int checkedCount = produitFavorisListView.getCheckedItemCount();
// Set the CAB title according to total checked items
mode.setTitle(checkedCount + " Selected");
// Calls toggleSelection method from ListViewAdapter Class
adapterFavoris.toggleSelection(position);
}
});
}
}
Here is my ListView adapter code:
public class ProduitsAdapter extends ArrayAdapter<StackProduits> {
/**
* fields
*/
ImageLoader imageLoader;
DisplayImageOptions options;
List<StackProduits> productList;
SparseBooleanArray mSelectedItemsIds;
/**
* Constructor.
*
* @param ctx
* @param textViewResourceId
* @param sites
*/
public ProduitsAdapter(Context ctx, int textViewResourceId, List<StackProduits> sites) {
super(ctx, textViewResourceId, sites);
//Setup the ImageLoader, we'll use this to display our images
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(ctx).build();
imageLoader = ImageLoader.getInstance();
imageLoader.init(config);
mSelectedItemsIds = new SparseBooleanArray();
//Setup options for ImageLoader so it will handle caching for us.
options = new DisplayImageOptions.Builder()
.cacheInMemory()
.cacheOnDisc()
.build();
}
/**
* This method is responsible for creating row views out of a StackProduits object that can be put
* into our ListView.
* <p/>
* (non-Javadoc)
*
* @see android.widget.ArrayAdapter#getView(int, android.view.View, android.view.ViewGroup)
*/
@Override
public View getView(int pos, View convertView, ViewGroup parent) {
RelativeLayout row = (RelativeLayout) convertView;
//Log.i("StackSites", "getView pos = " + pos);
if (null == row) { //No recycled View, we have to inflate one.
LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = (RelativeLayout) inflater.inflate(R.layout.item_row, null);
}
//Get our View References from item_row.xml
final ImageView iconImg = (ImageView) row.findViewById(R.id.iconImg);
TextView txtDesignation = (TextView) row.findViewById(R.id.nameTxt);
TextView txtAbout = (TextView) row.findViewById(R.id.aboutTxt);
TextView txtPrice = (TextView) row.findViewById(R.id.priceTxt);
TextView txtTotalArea = (TextView) row.findViewById(R.id.areaTxt);
final ProgressBar indicator = (ProgressBar) row.findViewById(R.id.progress);
//Initially we want the progress indicator visible, and the image invisible
indicator.setVisibility(View.VISIBLE); //show progress indicator
iconImg.setVisibility(View.INVISIBLE); //make image invisible
//Setup a listener we can use to switch from the loading indicator to the Image once it's ready
//changed ImageLoadingListener with SimpleImageLoadingListener
SimpleImageLoadingListener listener = new SimpleImageLoadingListener() {
@Override
public void onLoadingStarted(String arg0, View arg1) {
// TODO Auto-generated method stub
}
@Override
public void onLoadingCancelled(String arg0, View arg1) {
// TODO Auto-generated method stub
}
@Override
public void onLoadingComplete(String arg0, View arg1, Bitmap arg2) {
indicator.setVisibility(View.INVISIBLE);
iconImg.setVisibility(View.VISIBLE);
}
@Override
public void onLoadingFailed(String arg0, View arg1, FailReason arg2) {
// TODO Auto-generated method stub
}
};
//Load the image and use our options so caching is handled.
imageLoader.displayImage(getItem(pos).getImgUrl(), iconImg, options, listener);
//Set the relevant text in our TextViews (ListView)
txtDesignation.setText(getItem(pos).getDesignation());
txtAbout.setText(getItem(pos).getAbout());
txtPrice.setText(getItem(pos).getPrice());
txtTotalArea.setText(getItem(pos).getArea());
//return view that represents the full row
return row;
}
@Override
public void remove(StackProduits object) {
productList.remove(object);
notifyDataSetChanged();
}
public void removeSelection() {
mSelectedItemsIds = new SparseBooleanArray();
notifyDataSetChanged();
}
public void toggleSelection(int position) {
selectView(position, !mSelectedItemsIds.get(position));
}
public void selectView(int position, boolean value) {
if (value)
mSelectedItemsIds.put(position, value);
else
mSelectedItemsIds.delete(position);
notifyDataSetChanged();
}
public SparseBooleanArray getSelectedIds() {
return mSelectedItemsIds;
}
}
Here is my logcat:
07-08 11:51:28.106 23049-23049/com.example.adam_jaamour.cfimmobilier W/System: ClassLoader referenced unknown path: /data/app/com.example.adam_jaamour.cfimmobilier-1/lib/x86
07-08 11:51:28.260 23049-23049/com.example.adam_jaamour.cfimmobilier W/art: Before Android 4.1, method android.graphics.PorterDuffColorFilter android.support.graphics.drawable.VectorDrawableCompat.updateTintFilter(android.graphics.PorterDuffColorFilter, android.content.res.ColorStateList, android.graphics.PorterDuff$Mode) would have incorrectly overridden the package-private method in android.graphics.drawable.Drawable
07-08 11:51:28.437 23049-23087/com.example.adam_jaamour.cfimmobilier D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
[ 07-08 11:51:28.443 23049:23049 D/ ]
HostConnection::get() New Host Connection established 0xac33dfb0, tid 23049
[ 07-08 11:51:28.484 23049:23087 D/ ]
HostConnection::get() New Host Connection established 0xac33e100, tid 23087
07-08 11:51:28.487 23049-23087/com.example.adam_jaamour.cfimmobilier I/OpenGLRenderer: Initialized EGL, version 1.4
07-08 11:51:35.513 23049-23049/com.example.adam_jaamour.cfimmobilier E/libEGL: call to OpenGL ES API with no current context (logged once per thread)
07-08 11:51:35.664 23049-23059/com.example.adam_jaamour.cfimmobilier I/art: Background sticky concurrent mark sweep GC freed 14665(1431KB) AllocSpace objects, 16(572KB) LOS objects, 16% free, 8MB/10MB, paused 9.804ms total 28.247ms
07-08 11:51:35.735 23049-23087/com.example.adam_jaamour.cfimmobilier E/Surface: getSlotFromBufferLocked: unknown buffer: 0xa9e5ee50
07-08 11:51:38.871 23049-23049/com.example.adam_jaamour.cfimmobilier W/ViewRootImpl: Cancelling event due to no window focus: MotionEvent { action=ACTION_CANCEL, actionButton=0, id[0]=0, x[0]=316.2744, y[0]=1459.6875, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=13426969, downTime=13423514, deviceId=0, source=0x1002 }
07-08 11:51:38.872 23049-23049/com.example.adam_jaamour.cfimmobilier W/ViewRootImpl: Cancelling event due to no window focus: MotionEvent { action=ACTION_CANCEL, actionButton=0, id[0]=0, x[0]=316.2744, y[0]=1459.6875, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=13426969, downTime=13423514, deviceId=0, source=0x1002 }
07-08 11:51:38.872 23049-23049/com.example.adam_jaamour.cfimmobilier W/ViewRootImpl: Cancelling event due to no window focus: MotionEvent { action=ACTION_CANCEL, actionButton=0, id[0]=0, x[0]=316.2744, y[0]=1459.6875, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=13426969, downTime=13423514, deviceId=0, source=0x1002 }
07-08 11:51:38.872 23049-23049/com.example.adam_jaamour.cfimmobilier W/ViewRootImpl: Cancelling event due to no window focus: MotionEvent { action=ACTION_CANCEL, actionButton=0, id[0]=0, x[0]=316.2744, y[0]=1459.6875, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=13426969, downTime=13423514, deviceId=0, source=0x1002 }
07-08 11:51:42.399 23049-23049/com.example.adam_jaamour.cfimmobilier D/AndroidRuntime: Shutting down VM
--------- beginning of crash
07-08 11:51:42.401 23049-23049/com.example.adam_jaamour.cfimmobilier E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.adam_jaamour.cfimmobilier, PID: 23049
java.lang.NullPointerException: Attempt to invoke interface method 'boolean java.util.List.remove(java.lang.Object)' on a null object reference
at com.example.adam_jaamour.cfimmobilier.Produits.ProduitsAdapter.remove(ProduitsAdapter.java:144)
at com.example.adam_jaamour.cfimmobilier.Favoris$2.onActionItemClicked(Favoris.java:138)
at android.widget.AbsListView$MultiChoiceModeWrapper.onActionItemClicked(AbsListView.java:6242)
at com.android.internal.policy.PhoneWindow$DecorView$ActionModeCallback2Wrapper.onActionItemClicked(PhoneWindow.java:3540)
at android.support.v7.view.SupportActionModeWrapper$CallbackWrapper.onActionItemClicked(SupportActionModeWrapper.java:168)
at android.support.v7.app.AppCompatDelegateImplV7$ActionModeCallbackWrapperV7.onActionItemClicked(AppCompatDelegateImplV7.java:1750)
at android.support.v7.app.AppCompatDelegateImplV7$ActionModeCallbackWrapperV7.onActionItemClicked(AppCompatDelegateImplV7.java:1750)
at android.support.v7.view.StandaloneActionMode.onMenuItemSelected(StandaloneActionMode.java:136)
at android.support.v7.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:811)
at android.support.v7.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152)
at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:958)
at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:948)
at android.support.v7.widget.ActionMenuView.invokeItem(ActionMenuView.java:618)
at android.support.v7.view.menu.ActionMenuItemView.onClick(ActionMenuItemView.java:139)
at android.view.View.performClick(View.java:5198)
at android.view.View$PerformClick.run(View.java:21147)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
If you could help me in anyway possible it would be greatly appreaciated, I've been stuck on this for a few hours and can't find my mistake. I checked everywhere and have no idea why that null pointer exception is coming up!
Edit: for the user who flagged the question as a duplicate, I do know how to solve a null pointer exception, here the issue was finding what was causing it.