I hacked together my first app from a bunch of tutorials. With the help of one of them I implemented a RecyclerView inside a fragment which is used by the main activity. Now I found another tutorial which I want to use to implement multi-selection with the contextual action mode. The big problem is, that the tutorial isn't using a fragment. I tried to rewrite the code to fit in a fragment. I'm now at a point where I don't get any errors in Android Studio and the app runs, but when I actually do the LongClick to select an item, it crashes with a NullPointerException:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.view.ActionMode.setTitle(java.lang.CharSequence)' on a null object reference
I'm totally lost as to why this happens and with the whole multi-selection stuff in general.
I think the relevant parts of each involved classes are:
Adapter.java:
public class Adapter extends SelectableAdapter<Adapter.ViewHolder> {
private ArrayList<Meal> items = new ArrayList<>();
private Context context;
private ViewHolder.ClickListener clickListener;
public Adapter(ViewHolder.ClickListener clickListener) {
super();
this.clickListener = clickListener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_today_items, parent, false);
return new ViewHolder(v, clickListener);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Meal currentMeal = items.get(position);
holder.meal_typeicon.setImageResource(currentMeal.getBadgeIcon());
holder.meal_price.setText(currentMeal.getPriceOutput());
holder.meal_name.setText(isSelected(position) ? "SELECTED" : "NOT SELECTED");
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
@SuppressWarnings("unused")
private static final String TAG = ViewHolder.class.getSimpleName();
private RelativeLayout meal_item;
private ClickListener listener;
private TextView meal_name;
private TextView meal_price;
private TextView meal_contents;
private TextView meal_contents_spelledout;
private ImageView meal_typeicon;
public ViewHolder(View itemView, ClickListener listener) {
super(itemView);
meal_item = (RelativeLayout) itemView.findViewById(R.id.meal_item);
this.listener = listener;
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
meal_name = (TextView) itemView.findViewById(R.id.meal_name);
meal_price = (TextView) itemView.findViewById(R.id.meal_price);
meal_contents = (TextView) itemView.findViewById(R.id.meal_contents);
meal_contents_spelledout = (TextView) itemView.findViewById(R.id.meal_contents_spelledout);
meal_typeicon = (ImageView) itemView.findViewById(R.id.meal_typeicon);
}
@Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClicked(getAdapterPosition());
}
}
@Override
public boolean onLongClick(View v) {
if (listener != null) {
return listener.onItemLongClicked(getAdapterPosition());
}
return false;
}
public interface ClickListener {
void onItemClicked(int position);
boolean onItemLongClicked(int position);
}
}
@Override
public int getItemCount() {
return items.size();
}
public void setMealList(ArrayList<Meal> listMeals) {
this.items = listMeals;
notifyDataSetChanged();
notifyItemRangeChanged(0, listMeals.size());
}
}
SelectableAdapter.java:
public abstract class SelectableAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
@SuppressWarnings("unused")
private static final String TAG = SelectableAdapter.class.getSimpleName();
private SparseBooleanArray selectedItems;
public SelectableAdapter() {
selectedItems = new SparseBooleanArray();
}
/**
* Indicates if the item at position position is selected
* @param position Position of the item to check
* @return true if the item is selected, false otherwise
*/
public boolean isSelected(int position) {
return getSelectedItems().contains(position);
}
/**
* Toggle the selection status of the item at a given position
* @param position Position of the item to toggle the selection status for
*/
public void toggleSelection(int position) {
if (selectedItems.get(position, false)) {
selectedItems.delete(position);
} else {
selectedItems.put(position, true);
}
notifyItemChanged(position);
}
/**
* Clear the selection status for all items
*/
public void clearSelection() {
List<Integer> selection = getSelectedItems();
selectedItems.clear();
for (Integer i : selection) {
notifyItemChanged(i);
}
}
/**
* Count the selected items
* @return Selected items count
*/
public int getSelectedItemCount() {
return selectedItems.size();
}
/**
* Indicates the list of selected items
* @return List of selected items ids
*/
public List<Integer> getSelectedItems() {
List<Integer> items = new ArrayList<>(selectedItems.size());
for (int i = 0; i < selectedItems.size(); ++i) {
items.add(selectedItems.keyAt(i));
}
return items;
}
}
FragmentToday.java:
public class FragmentToday extends Fragment implements Adapter.ViewHolder.ClickListener{
private RequestQueue requestQueue;
public ArrayList<Meal> listMeals = new ArrayList<>();
private TextView textVolleyError;
private MealSorter mSorter = new MealSorter();
private Adapter adapter;
private ActionModeCallback actionModeCallback = new ActionModeCallback();
private ActionMode actionMode;
public static FragmentToday newInstance() {
return new FragmentToday();
}
public FragmentToday() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
VolleySingleton volleySingleton = VolleySingleton.getInstance();
requestQueue = volleySingleton.getRequestQueue();
sendJsonRequest();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_today, container, false);
textVolleyError = (TextView) view.findViewById(R.id.textVolleyError);
adapter = new Adapter(this);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setAdapter(adapter);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
sendJsonRequest();
return view;
}
@Override
public void onItemClicked(int position) {
if (actionMode != null) {
toggleSelection(position);
}
}
@Override
public boolean onItemLongClicked(int position) {
if (actionMode != null) {
((AppCompatActivity) getActivity()).startSupportActionMode(actionModeCallback);
}
toggleSelection(position);
return true;
}
private void toggleSelection(int position) {
adapter.toggleSelection(position);
int count = adapter.getSelectedItemCount();
if (count == 0) {
actionMode.finish();
} else {
actionMode.setTitle(String.valueOf(count));
actionMode.invalidate();
}
}
private class ActionModeCallback implements ActionMode.Callback {
@SuppressWarnings("unused")
private final String TAG = ActionModeCallback.class.getSimpleName();
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate (R.menu.menu_cam, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.favorite:
// TODO: actually remove items
Log.d(TAG, "menu_remove");
mode.finish();
return true;
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode mode) {
adapter.clearSelection();
actionMode = null;
}
}
}
The problem seem to be from your onItemLongClicked
method of the FragmentToday.java
class. It should have been:
@Override
public boolean onItemLongClicked(int position) {
if (actionMode == null) {
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(actionModeCallback);
}
toggleSelection(position);
return true;
}
instead of:
@Override
public boolean onItemLongClicked(int position) {
if (actionMode != null) {
((AppCompatActivity) getActivity()).startSupportActionMode(actionModeCallback);
}
toggleSelection(position);
return true;
}