-2

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.

Adam Jaamour
  • 1,326
  • 1
  • 15
  • 31
  • Possible duplicate of [What is a NullPointerException, and how do I fix it?](http://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – Janki Gadhiya Jul 08 '16 at 11:23

3 Answers3

1

In the code shown, you aren't initializing productList, so you get a nullPointer.

Should be in the constructor:

public ProduitsAdapter(Context ctx, int textViewResourceId, List<StackProduits> sites) {
    super(ctx, textViewResourceId, sites);
    this.productList = sites;//////////here 
    //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();
}
user6547359
  • 204
  • 1
  • 9
1

You get the NullPointerException because in the ProduitsAdapter class you haven't initialised the productList variable. You can either initialise it in the constructor like this:

public ProduitsAdapter(Context ctx, int textViewResourceId, List<StackProduits> sites) {
super(ctx, textViewResourceId, sites);
productList = 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();
}

or you can remove completely the variable, as you are already storing the list in a variable in the parent class. But then you would have to update your "remove" method to something like this:

@Override
public void remove(StackProduits object) {
    super.remove(object);
    notifyDataSetChanged();
}

I prefer the second solution as you will not duplicate the list of StackProduits.

1

You have to modify your onActionItemClicked()

Look on commented area you are removing item and then update notifyDataSetChanged() every time you dont have to do this. notifyDataSetChanged() only once.

Comment the below as i commented.

And also i think you have to change your for loop logic.

        @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);

                            // Look here you remove all item item from list
                            // and then update notifyDataSetChanged() only once.                           
                            // Comment the below as i commented.
                            //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;
            }
        }
Sohail Zahid
  • 8,099
  • 2
  • 25
  • 41