I'm currently trying to implement the new recyclerview-selection APIs from Android Support Library 28.0.0-alpha1, and am running into some issues. My goal is to have a RecyclerView, with the ability to select multiple rows, show a Contextual Action Bar, and perform actions on them, such as "delete" or "share"
I'll try and furnish enough code to give a good idea of what's going on, but I can always respond with more if necessary.
In my Fragment which contains the RecyclerView I'm concerned with, I am initiating a SelectionTracker, and setting it on my RecyclerView.Adapter, like so:
private void buildRecyclerView() {
sheetsAdapter = new SheetsAdapter(getContext(), this, sheets);
gridManager = new GridLayoutManager(getContext(), getResources().getInteger(R.integer.grid_span_count));
ItemOffsetDecoration itemDecoration = new ItemOffsetDecoration(getContext(), R.dimen.item_offset);
sheetsRecycler.addItemDecoration(itemDecoration);
sheetsRecycler.setLayoutManager(gridManager);
sheetsRecycler.setAdapter(sheetsAdapter);
sheetsRecycler.setHasFixedSize(true);
SelectionTracker selectionTracker = new SelectionTracker.Builder<>(
"sheet_selection",
sheetsRecycler,
new StableIdKeyProvider(sheetsRecycler),
new SheetDetailsLookup(sheetsRecycler),
StorageStrategy.createLongStorage()
)
.withOnContextClickListener(this)
.build();
sheetsAdapter.setSelectionTracker(selectionTracker);
}
This Fragment also implements OnContextClickListener, in order to listen for long-clicks on the items in my RecyclerView:
@Override
public boolean onContextClick(@NonNull MotionEvent e) {
if (actionMode != null) {
return false;
}
// Start the CAB using the ActionMode.Callback defined below
if (getActivity() != null) {
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(actionModeCallback);
}
return true;
}
And it should show my CAB, like this:
private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.sheets_cab_menu, 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.delete:
Toast.makeText(getContext(), R.string.sheets_delete, Toast.LENGTH_SHORT).show();
mode.finish();
return true;
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode mode) {
actionMode = null;
}
};
My SheetDetailsLookup looks like this:
public class SheetDetailsLookup extends ItemDetailsLookup<Long> {
private RecyclerView recyclerView;
SheetDetailsLookup(RecyclerView recyclerView) {
super();
this.recyclerView = recyclerView;
}
@Nullable
@Override
public ItemDetails<Long> getItemDetails(@NonNull MotionEvent e) {
View view = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (view != null) {
RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(view);
if (holder instanceof SheetsAdapter.SheetViewHolder) {
return ((SheetsAdapter.SheetViewHolder) holder).getItemDetails();
}
}
return null;
}
}
And in my SheetViewHolder, I update the view to show that it has been selected:
if (selectionTracker.isSelected(sheet.uid)) {
layout.setBackgroundResource(R.color.md_grey_700);
} else {
layout.setBackgroundResource(android.R.color.transparent);
}
As well as:
public SheetItemDetails getItemDetails() {
return new SheetItemDetails(getAdapterPosition(), mSheets.get(getAdapterPosition()).uid);
}
Where SheetItemDetails is simply:
public class SheetItemDetails extends ItemDetailsLookup.ItemDetails<Long> {
private int position;
private Long key;
SheetItemDetails(int position, Long key) {
this.position = position;
this.key = key;
}
@Override
public int getPosition() {
return position;
}
@Nullable
@Override
public Long getSelectionKey() {
return key;
}
}
I've implemented all of the things mentioned in the API specification, but am now running into troubles. My CAB doesn't show up when I select an item... and the app usually crashes. Crashes occur whenever I try to "back out" of selections,and then long-click to start another selection, with this stack trace:
java.lang.IllegalStateException
at android.support.v4.util.Preconditions.checkState(Preconditions.java:130)
at android.support.v4.util.Preconditions.checkState(Preconditions.java:142)
at androidx.recyclerview.selection.GestureSelectionHelper.start(GestureSelectionHelper.java:76)
at androidx.recyclerview.selection.SelectionTracker$Builder$4.run(SelectionTracker.java:742)
at androidx.recyclerview.selection.TouchInputHandler.onLongPress(TouchInputHandler.java:136)
at androidx.recyclerview.selection.GestureRouter.onLongPress(GestureRouter.java:95)
at android.view.GestureDetector.dispatchLongPress(GestureDetector.java:779)
at android.view.GestureDetector.access$200(GestureDetector.java:40)
at android.view.GestureDetector$GestureHandler.handleMessage(GestureDetector.java:293)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)
Also, I've now lost the ability to "short-click" on one of my items, to launch a detail view... Which I had working just fine until now.
What have I done wrong?