30

This is how I navigate through my app:

  • Open fragment with list
  • Filter list by a text entered in searchview
  • Tap on listitem (list fragment gets replaced by detail fragment)
  • Navigate back (detail fragment gets replaced by list fragment)

When I navigate from list- to detail-fragment, I want to keep the current filter of the searchview in a string variable. I store the value of the searchview when onQueryTextChange is executed.

The problem: I can't store the actual filter-value because onQueryTextChange gets called when I navigate from list to detail because something cleared the text of the searchview.

// ...
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String s) {
            return false;
        }

        @Override
        public boolean onQueryTextChange(String s) {
            searchReceived(s);
            return true;
        }
    });
// ...

public void searchReceived(String searchQuery)
{
    this.stateHolder.searchQuery = searchQuery;
    // more code...
}

When I want to restore the filter when navigating back, it just filters with an empty string because the wrong value got stored in this.stateHolder.searchQuery.

Stack:

onQueryTextChange():139, EmployeeListFragment$1 {com.example.exampleapp.fragment}
onTextChanged():1153, SearchView {android.widget}
access$2000():92, SearchView {android.widget}
onTextChanged():1638, SearchView$11 {android.widget}
sendOnTextChanged():7408, TextView {android.widget}
setText():3816, TextView {android.widget}
setText():3671, TextView {android.widget}
setText():80, EditText {android.widget}
setText():3646, TextView {android.widget}
setQuery():511, SearchView {android.widget}
onActionViewCollapsed():1250, SearchView {android.widget}
collapseItemActionView():1662, ActionBarView$ExpandedActionViewMenuPresenter {com.android.internal.widget}
collapseItemActionView():1258, MenuBuilder {com.android.internal.view.menu}
clear():521, MenuBuilder {com.android.internal.view.menu}
doInvalidatePanelMenu():789, PhoneWindow {com.android.internal.policy.impl}
run():221, PhoneWindow$1 {com.android.internal.policy.impl}

How can I prevent the searchview from being cleared by the system when navigating?

Thanks.

Tom
  • 489
  • 4
  • 10
  • Did you find any solution to this problem? – starrystar Jul 22 '17 at 19:01
  • @starrystar I posted a possible workaround here: https://stackoverflow.com/questions/23544382/fragment-replacement-triggers-onquerytextchange-on-searchview#23558681 – Tom Aug 26 '17 at 09:19

9 Answers9

7

After 2 days of Googling i got the solution that will definitely helps you.

I have just cut and pasted code from onCreateOptionMenu() to onPrepareOptionMenu()

I do not know why it happens, I think listener of searchView is not going to be NULL by the way you can change your code this way.

@Override
public void onPrepareOptionsMenu(Menu menu) {
    super.onPrepareOptionsMenu(menu);
    getSherlockActivity().getSupportMenuInflater().inflate(R.menu.menu_all_order, menu);

    searchView = (SearchView) menu.findItem(R.id.menu_all_order_search).getActionView();
    searchView.setInputType(InputType.TYPE_CLASS_NUMBER);
    searchView.setQueryHint("Enter Order No");
    searchView.setOnQueryTextListener(this);
}

and Remove:

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
}

Thanks :)

Pratik Butani
  • 60,504
  • 58
  • 273
  • 437
6

The problem is ActionBar (and subcomponents) are collapsed when the fragment holding it is replaced. When this happens the SearchView query is cleared as seen in onActionViewCollapsed. Workarounds include:

Prevent collapsing

Remove setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); from the searchview's creation code or android:showAsAction="collapseActionView" from its xml code.

This will affect how/when the view's close/voicesearch buttons are displayed upon attempting to collapse it

It would also be possible to prevent collapsing by overriding SearchView's onActionViewCollapsed() method to do nothing but would accomplish the same.

Ignore query changes when appropriate

When the fragment is not valid (being replaced) then ignore query changes that would otherwise cause the fragment to alter its saved query text or layout (changing the layout would cause an exception if the fragment doesn't actually have view content).

public boolean onQueryTextChange(String newText) {
    if (!isVisible()) {
        // The fragment was replaced so ignore
        return true;
    }
    // Text was actually changed, perform search
    return true;
}

This would be the better option as it doesn't affect the functionality of the searchview.

alt
  • 69
  • 1
  • 3
2

I see you didn't find a solution yet, I have same problem posted here:

I found that when invalidateOptionsMenu is called also the method onQueryTextChange is called because the View launch dispatchRestoreInstanceState with the previous value after you've cleared the search for example. Do you call invalidateOptionsMenu maybe?

Maybe, inside onQueryTextChange, you can check a boolean on some current state of your application objects to know if the method content should be executed. In my case I use mDrawerLayout.isDrawerOpen(..) to allow the search.

Also you can implement the SearchView.OnQueryTextListener on each Class you use, on a Fragment or on the Main Activity class, somewhere in the class you could set a boolean module variable that you will check inside onQueryTextChange.

Community
  • 1
  • 1
Davideas
  • 3,226
  • 2
  • 33
  • 51
1

I modified the menu item attribute showAsAction from android:showAsAction="always|collapseActionView" to android:showAsAction="always". Now the look and behavior of the searchview has changed a bit, but the searchQuery doesn't get cleared!

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
            android:id="@+id/actionSearch"
            android:icon="@android:drawable/ic_menu_search"
            android:actionViewClass="android.widget.SearchView"
            android:showAsAction="always"
            android:title="@android:string/search_go"
    />
</menu>
Tom
  • 489
  • 4
  • 10
1

Using androidx.appcompat.widget.SearchView + LiveData + ViewModel :

searchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener {
    override fun onQueryTextSubmit(query: String?): Boolean {
        viewModel.setSearchQuery(query)
        return true
    }
    override fun onQueryTextChange(newText: String?): Boolean {
        if(searchView.isIconified || !isVisible) {
            // Don't call setSearchQuery when SearchView is collapsing/collapsed
            return true
        }
        viewModel.setSearchQuery(newText)
        return true
    }
})
maxbeaudoin
  • 6,546
  • 5
  • 38
  • 53
1

If anyone experiences this same issue, the onQueryTextChange callback will be fired when replacing the Fragment when using the following action in your menu XML file when your SearchView is currently expanded:

android:showAsAction="always|collapseActionView"

When you attempt to use isVisible() (Java) or isVisible (Kotlin) in the onQueryTextChange callback as a way to determine if the Fragment is actually visible or not, the value returned will always return true.

The reason for this is that the onQueryTextChange callback will be fired when you're SearchView is currently expanded when you attempt to change Fragment's.

The default action of collapseActionView at this stage is to collapse the SearchView when changing Fragment's.

Solution:

Simply call your SearchView !isIconified (Kotlin) method in your onQueryTextChange callback and only set your logic if it returns false, i.e. the SearchView is currently expanded. Full example described below:

yourSearchView.setOnQueryTextListener(object: SearchView.OnQueryTextListener {
    override fun onQueryTextSubmit(query: String?): Boolean {
        // Do search query here if you use a submit action
        return true
    }

    override fun onQueryTextChange(newText: String?): Boolean {
        if (!yourSearchView.isIconified) {
            // Don't execute your search query here when SearchView is 
            // currently open
            return true
        }
        // Do search query here
        return true
    }
})
Richard Ansell
  • 916
  • 1
  • 12
  • 28
0
override fun onQueryTextChange(newText: String?): Boolean {
   // your codes
   if (!newText.isNullOrBlank()) {
      // when fragment change
      // this will not work
   }
   // your codes
   return true
}

When fragment changes searchView triggered I don't know why but by checking this newText.isNullOrBlank() I solved my problem maybe it can help someone

ZootHii
  • 800
  • 1
  • 8
  • 16
0

All the proposed solutions were unfortunately not helpful on my side, because I had to keep the collapseActionView|ifRoom attributes on my SearchView, and I didn't want to check nullability/emptiness of my SearchView query.

So everytime the query changes, or the fragment is replaced by another, the onQueryTextChange callback is executed -> and the query is emptied (which causes some issues ) So I've done this :

searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
   override fun onQueryTextSubmit(query: String): Boolean {
      return false
   }

   override fun onQueryTextChange(newText: String): Boolean {
      if (this@SearchFragment.isRemoving) {
         return true
      }

      // [Statements here]
      return true
   }
})

I'm checking if my Fragment is currently being removed from the Activity with isRemoving, and I directly return true in order to "do nothing" if the fragment is being removed/replaced by another.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Kilian P.
  • 650
  • 1
  • 5
  • 9
-2

This is my solution:

 @Override
        public boolean onQueryTextChange(final String newText) {

            if (searchTF.isIconified())
                return false;

            // put the "real" onQueryTextChange actions here


            return true;


        }