In my app, I have a SearchView
in the Toolbar
.
When the user clicks the search icon, the SearchView expands and the user starts typing. The search query filters a RecyclerView
list by title. This all works fine.
However, the filter function stops working when I perform an OnLongClick action on one of the RecyclerView
items.
I have both an OnClickListener and OnLongClickListener attached to each RecyclerView
holder, but it's only the OnLongClick action that stops the SearchView
from filtering. I don't understand why. I've tried reinstantiating the SearchView
and resetting the RecyclerView
to no avail.
Here's my code:
Functions that don't affect the SearchView have been removed for the sake of space.
MainActivity.java
public class MainActivity extends AppCompatActivity {
//LONG CLICK ACTION MODE VARIABLES
boolean isInActionMode = false;
TextView selectedCounterText;
//SEARCH BAR
SearchView searchView;
//INSERT DIALOG TEXTVIEWS
EditText editTitle, editCategory, editSignifier, editDate, editRecurs, editDetails;
Button btnCreate, btnCancel;
RecyclerView recyclerView;
RecyclerView.LayoutManager layoutManager;
CardAdapter adapter;
Toolbar toolbar;
ArrayList<Bullet> bullets = new ArrayList<>();
ArrayList<Bullet> selectedBullets = new ArrayList<>();
int counter = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//SET DEFAULT VIEW STATES
selectedCounterText = (TextView) findViewById(R.id.selected_counter);
appName = (TextView) findViewById(R.id.app_name);
selectedCounterText.setVisibility(View.GONE);
appName.setVisibility(View.VISIBLE);
noBulletsMessage = (TextView) findViewById(R.id.noBullets_message);
noBulletsMessage.setVisibility(View.GONE);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//RECYCLERVIEW PROPERTIES
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setHasFixedSize(true);
//ACTION MODE ON LONG CLICK VIEW STATES
selectedCounterText = (TextView) findViewById(R.id.selected_counter);
selectedCounterText.setVisibility(View.GONE);
//ADAPTER
adapter = new CardAdapter(this, bullets);
//RETRIEVE DATA
retrieveData();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_activity_main, menu);
//SEARCH
final MenuItem searchItem = menu.findItem(R.id.item_search);
searchView = (SearchView) searchItem.getActionView();
searchView.setIconifiedByDefault(true);
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
@Override
public boolean onClose() {
return false;
}
});
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String query) {
//FILTER AS YOU TYPE
adapter.getFilter().filter(query);
return false;
}
});
return true;
}
@Override
protected void onResume() {
super.onResume();
retrieveData();
}
/**
* @param item
* @return This method includes behavior for all action toolbar menu items: Add, search, edit,
* and delete.
* <p>
* It detects which button is pressed and performs the appropriate action.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//IF HOME (BACK ARROW) IS PRESSED
} else if (item.getItemId() == android.R.id.home) {
clearActionMode();
adapter.notifyDataSetChanged();
}
return true;
}
public void clearActionMode() {
isInActionMode = false;
toolbar.getMenu().clear();
toolbar.inflateMenu(R.menu.menu_activity_main);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
selectedCounterText.setVisibility(View.GONE);
appName.setVisibility(View.VISIBLE);
selectedCounterText.setText("0 Item(s) Selected");
counter = 0;
selectedBullets.clear();
}
@Override
public void onBackPressed() {
if (isInActionMode) {
clearActionMode();
adapter.notifyDataSetChanged();
} else {
super.onBackPressed();
}
}
}
CardHolder.java
public CardHolder(final View itemView, final MainActivity mainActivity) {
super(itemView);
signifier_img = (ImageView) itemView.findViewById(R.id.img_id);
titleText = (TextView) itemView.findViewById(R.id.title);
categoryText = (TextView) itemView.findViewById(R.id.category);
cardView = (CardView) itemView.findViewById(R.id.bulletCardView);
checkBox = (CheckBox) itemView.findViewById(R.id.check_list_item);
this.mainActivity = mainActivity;
//CLICK LISTENERS
cardView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
mainActivity.toolbar.getMenu().clear();
mainActivity.toolbar.inflateMenu(R.menu.menu_action_mode);
mainActivity.selectedCounterText.setVisibility(View.VISIBLE);
mainActivity.appName.setVisibility(View.GONE);
mainActivity.isInActionMode = true;
mainActivity.adapter.notifyDataSetChanged();
mainActivity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
return true;
}
});
cardView.setOnClickListener(this);
checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mainActivity.prepareSelection(view, getAdapterPosition());
}
});
}
@Override
public void onClick(View view) {
this.itemClickListener.onItemClick(view, getLayoutPosition());
}
public void setItemClickListener(ItemClickListener itemClick) {
this.itemClickListener = itemClick;
}
}
CardAdapter.java
public class CardAdapter extends RecyclerView.Adapter<CardHolder> implements Filterable {
Context context;
ArrayList<Bullet> bullets, filterList;
SearchFilter filter;
MainActivity mainActivity;
public CardAdapter(Context context, ArrayList<Bullet> bullets) {
this.context = context;
this.bullets = bullets;
this.filterList = bullets;
mainActivity = (MainActivity) context;
}
//INITIALIZE VIEWHOLDER
@Override
public CardHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//VIEW OBJ FROM XML
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_card_view, parent, false);
//HOLDER
CardHolder holder = new CardHolder(view, mainActivity);
return holder;
}
//BIND DATA TO VIEWS
@Override
public void onBindViewHolder(final CardHolder holder, int position) {
holder.signifier_img.setImageResource(R.drawable.asterisk_48px);
holder.titleText.setText(bullets.get(position).getTitle());
if (!mainActivity.isInActionMode) {
holder.checkBox.setVisibility(View.GONE);
} else {
holder.checkBox.setVisibility(View.VISIBLE);
holder.checkBox.setChecked(false);
}
//CARD CLICKED
holder.setItemClickListener(new ItemClickListener() {
@Override
public void onItemClick(View view, int position) {
//DISPLAY POPUP OF FULL INFO OF BULLET
//OPEN DETAIL VIEW
//PASS DATA TO VIEW
if (!mainActivity.isInActionMode) {
//CREATE INTENT
Intent intent = new Intent(context, DetailView.class);
//LOAD DATA TO INTENT
intent.putExtra("ID", bullets.get(position).getId());
intent.putExtra("TITLE", bullets.get(position).getTitle());
intent.putExtra("CATEGORY", bullets.get(position).getCategory());
intent.putExtra("SIGNIFIER", bullets.get(position).getSignifier());
intent.putExtra("DATE", bullets.get(position).getDate());
intent.putExtra("RECURS", bullets.get(position).getRecurs());
intent.putExtra("DETAILS", bullets.get(position).getDetails());
//START ACTIVITY
context.startActivity(intent);
} else {
//DO NOTHING
}
}
});
}
@Override
public int getItemCount() {
return bullets.size();
}
//RETURN FILTER OBJ
@Override
public Filter getFilter() {
if (filter == null) {
filter = new SearchFilter(filterList, this);
}
return filter;
}
}
SearchFilter.java
public class SearchFilter extends Filter {
CardAdapter adapter;
ArrayList<Bullet> filterList;
public SearchFilter(ArrayList<Bullet> filterList, CardAdapter adapter) {
this.adapter = adapter;
this.filterList = filterList;
}
//FILTER OCCURS HERE
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
//CHECK CONSTRAINT VALIDITY
if (constraint != null && constraint.length() > 0) {
//CHANGE TO UPPER
constraint = constraint.toString().toUpperCase();
//STORE FILTERED BULLETS
ArrayList<Bullet> filteredBullets = new ArrayList<>();
for (int i = 0; i < filterList.size(); i++) {
//CHECK
if (filterList.get(i).getTitle().toUpperCase().contains(constraint)) {
//ADD BULLET TO FILTERED BULLETS
filteredBullets.add(filterList.get(i));
}
}
results.count = filteredBullets.size();
results.values = filteredBullets;
} else {
results.count = filterList.size();
results.values = filterList;
}
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
adapter.bullets = (ArrayList<Bullet>) results.values;
//REFRESH RECYCLERVIEW
adapter.notifyDataSetChanged();
}
}
menu_activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/item_search"
android:icon="@drawable/ic_action_search"
android:title="Search..."
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="collapseActionView|ifRoom" />
<item
android:id="@+id/item_add"
android:icon="@drawable/ic_action_add2"
android:title="Add"
app:showAsAction="always" />
</menu>
I believe that's all the code that affects it, but if there's anything else you need let me know.
Here's the Stack Trace:
I don't really understand what it means, but it's what occurs when I try to filter some query after a LongClick Event.
09-07 01:46:38.184 17027-17104/com.curtiswhite.www.sqlitedatabaseforephemeris I/OpenGLRenderer: Initialized EGL, version 1.4 09-07 01:47:40.066 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length 09-07 01:47:40.066 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length 09-07 01:47:45.036 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length 09-07 01:47:45.036 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris E/SpannableStringBuilder: SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length 09-07 01:47:45.991 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection 09-07 01:47:45.991 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getSelectedText on inactive InputConnection 09-07 01:47:46.003 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection 09-07 01:47:46.003 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getTextBeforeCursor on inactive InputConnection 09-07 01:47:46.015 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getTextAfterCursor on inactive InputConnection 09-07 01:47:46.022 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection 09-07 01:47:46.023 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getSelectedText on inactive InputConnection 09-07 01:47:46.023 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection 09-07 01:47:46.023 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getTextBeforeCursor on inactive InputConnection 09-07 01:47:46.024 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getTextAfterCursor on inactive InputConnection 09-07 01:47:46.027 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection 09-07 01:47:46.027 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getSelectedText on inactive InputConnection 09-07 01:47:46.027 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection 09-07 01:47:46.028 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getTextBeforeCursor on inactive InputConnection 09-07 01:47:46.030 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getTextAfterCursor on inactive InputConnection 09-07 01:47:46.062 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection 09-07 01:47:46.062 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getSelectedText on inactive InputConnection 09-07 01:47:46.062 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection 09-07 01:47:46.063 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getTextBeforeCursor on inactive InputConnection 09-07 01:47:46.063 17027-17027/com.curtiswhite.www.sqlitedatabaseforephemeris W/IInputConnectionWrapper: getTextAfterCursor on inactive InputConnection
I solved the issue.
It turns out that the issue was caused when inflating the new menu onLongClick. This caused the SearchView
to deflate (as expected), but upon pressing back (OnBackPressed gets called), the original menu would inflate, but the SearchView
that was placed there was a new one which had not been instantiated.
To correct this, I simply called OnCreateMenuOptions(toolbar.getMenu()); in the onBackPressed method. This reinstantiates the SearchView
so it will work again after the menu inflates.