According to the official documentation, there are two ways of providing a search interface: using either the search dialog or a SearchView
widget. I'd like to pay attention to passing search context data using these two ways.
So, the documentation says:
..you can provide additional data in the intent that the system sends to
your searchable activity. You can pass the additional data in the
APP_DATA Bundle, which is included in the ACTION_SEARCH intent.
To pass this kind of data to your searchable activity, override the
onSearchRequested() method for the activity from which the user can
perform a search, create a Bundle with the additional data, and call
startSearch() to activate the search dialog. For example:
@Override
public boolean onSearchRequested() {
Bundle appData = new Bundle();
appData.putBoolean(SearchableActivity.JARGON, true);
startSearch(null, false, appData, false);
return true;
}
..Once the user submits a query, it's delivered to your searchable
activity along with the data you've added. You can extract the extra
data from the APP_DATA Bundle to refine the search. For example:
Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
if (appData != null) {
boolean jargon = appData.getBoolean(SearchableActivity.JARGON);
}
This refers to the search dialog. And what about the search widget?
Is it possible to pass search context data using the SearchView
widget only?
Hope, someone could give clear explanation and/or suggest another or similar way to accomplish the goal.
Thanks!
I've discovered the solution. Even two solutions!
They don't need to invoke onSearchRequested()
thus there is no search dialog at all :)
First, I provide some common steps to create the search interface and then give the solutions of the source problem.
We add the Search View to the App Bar by creating res/menu/options_menu.xml
file with the following code:
<?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"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" >
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_action_search"
android:title="@string/search_string"
app:showAsAction="collapseActionView|ifRoom"
app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>
Create a Searchable Configuration in res/xml/searchable.xml
file:
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name"
android:hint="@string/search_hint" />
Declare two activities in AndroidManifest.xml
:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.searchinterface">
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SearchableActivity"
android:label="@string/app_name"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
</application>
</manifest>
Create a Searchable Activity where we handle the ACTION_SEARCH
intent and the search context data, passed from the MainActivity
. We extract the extra data from the APP_DATA
Bundle
to refine the search:
public class SearchableActivity extends AppCompatActivity {
public static final String JARGON = "com.example.searchinterface.jargon";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_searchable);
handleIntent(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleIntent(intent);
}
private void handleIntent(Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
// use the query to search the data somehow
Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
if (appData != null) {
boolean jargon = appData.getBoolean(SearchableActivity.JARGON);
// use the context data to refine our search
}
}
}
}
Now, we need to implement our MainActivity
class. So, we inflate our menu and configure the SearchView
element as well. We also need to set the SearchView.OnQueryTextListener
and implement its methods, especially onQueryTextSubmit()
. It is invoked when the user presses the submit button and it contains the main logic of passing the search context data to the SearchableActivity
.
Finally, we reached the main answer section. As I said, there are two solutions:
1. Create an intent with Bundle
extra and send it to the SearchableActivity
manually;
Here is the MainActivity
with all necessary contents:
public class MainActivity extends AppCompatActivity implements SearchView.OnQueryTextListener {
private SearchView mSearchView;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.options_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
// associate searchable configuration with the SearchView
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
mSearchView.setSearchableInfo(searchManager.getSearchableInfo(
new ComponentName(this, SearchableActivity.class)));
mSearchView.setOnQueryTextListener(this);
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
Intent searchIntent = new Intent(this, SearchableActivity.class);
searchIntent.putExtra(SearchManager.QUERY, query);
Bundle appData = new Bundle();
appData.putBoolean(SearchableActivity.JARGON, true); // put extra data to Bundle
searchIntent.putExtra(SearchManager.APP_DATA, appData); // pass the search context data
searchIntent.setAction(Intent.ACTION_SEARCH);
startActivity(searchIntent);
return true; // we start the search activity manually
}
@Override
public boolean onQueryTextChange(String newText) {
return false;
}
}
Thanks to https://stackoverflow.com/a/22184137/6411150.
The second solution is also put in onQueryTextSubmit()
(but it's not necessary):
2. Create search context data Bundle
and pass it to the setAppSearchData()
method of the SearchView
.
So, we don't need to create and pass the whole search intent and launch respective searchable activity, the system will take care of it.
Here is another code snippet:
/*
You may need to suppress the “restrictedApi” error that you could possibly
receive from this method "setAppSearchData(appData)”.I had to
I’m targetSdkVersion 26. I’m also using Android Studio 3
with the new gradle plugin, which might be causing this.
If you’re not running Android Studio 3 you can simply put
“//noinspection RestrictedApi"
right above the line: mSearchView.setAppSearchData(appData);
*/
@SuppressWarnings("RestrictedApi")
@Override
public boolean onQueryTextSubmit(String query) {
Bundle appData = new Bundle();
appData.putBoolean(SearchableActivity.JARGON, true); // put extra data to Bundle
mSearchView.setAppSearchData(appData); // pass the search context data
return false; // we do not need to start the search activity manually, the system does it for us
}
Thanks to https://stackoverflow.com/a/38295904/6411150.
Note: Only support library's version of SearchView
(android.support.v7.widget.SearchView
) contains the setAppSearchData()
method, so be attentive.
If the design works for your needs, you can use the SearchView by itself, and add an OnQueryTextListener to it, and deal with it there. There is no need for anything else, no Intents, Meta-tags, nor XML files. I have done this a few times, and the docs are a bit not clear on this.