I am having a problem filtering a list using JSON data parsed via Volley based on this tutorial, when the list is filtered via the Search in App intent in Google's System Voice Actions from the Google App.
The exact problem encountered is listed below:
The app is initially not running at all (the search works perfectly if the app is running or in the background).
Fire the intent via adb:
cd C:...\android-sdk\platform-tools
adb shell am start -a "com.google.android.gms.actions.SEARCH_ACTION" --es query "[query keyword]" -n "com.testapp/.MainActivity"
The correct app opens but the list is empty, i.e. no results filtered.
The app then crashes.
Below is the stacktrace:
02-15 17:54:03.331: D/AndroidRuntime(31982): Shutting down VM
02-15 17:54:03.341: E/AndroidRuntime(31982): FATAL EXCEPTION: main
02-15 17:54:03.341: E/AndroidRuntime(31982): Process: com.test.app, PID: 31982
02-15 17:54:03.341: E/AndroidRuntime(31982): java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
02-15 17:54:03.341: E/AndroidRuntime(31982): at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
02-15 17:54:03.341: E/AndroidRuntime(31982): at java.util.ArrayList.get(ArrayList.java:308)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.test.app.adapter.CustomListAdapter.getView(CustomListAdapter.java:92)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.test.app$1.onResponse(MainActivity.java:152)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.test.app$1.onResponse(MainActivity.java:1)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.android.volley.toolbox.JsonRequest.deliverResponse(JsonRequest.java:65)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:99)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.os.Handler.handleCallback(Handler.java:739)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.os.Handler.dispatchMessage(Handler.java:95)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.os.Looper.loop(Looper.java:145)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.app.ActivityThread.main(ActivityThread.java:6843)
02-15 17:54:03.341: E/AndroidRuntime(31982): at java.lang.reflect.Method.invoke(Native Method)
02-15 17:54:03.341: E/AndroidRuntime(31982): at java.lang.reflect.Method.invoke(Method.java:372)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
My app includes a MainActivity which is the searchable activity that displays the list, a model class, an adapter class, and a controller class for the list items (similar to existing Android ListView tutorials).
Below is the code (some code omitted):
MainActivity
//[…]
public class MainActivity extends AppCompatActivity {
//[…]
private List<Item> itemList = new ArrayList<Item>();
public static ListView listView;
private CustomListAdapter adapter;
private static final String GMS_SEARCH_ACTION = "com.google.android.gms.actions.SEARCH_ACTION";
private String qq;
@Override
protected void onCreate(Bundle savedInstanceState) {
//[…]
onNewIntent(getIntent());
}
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String action = intent.getAction();
if (action!= null && (action.equals(Intent.ACTION_SEARCH)||action.equals(GMS_SEARCH_ACTION))) {
qq = intent.getStringExtra(SearchManager.QUERY);
doSearch(qq);
}
}
/* FIXME - Current problem: From adb command, ListView can only be filtered successfully only if app is still running in background (i.e. onPause()). ListView filtering works ok if searching within the app. If app is not running, sending the search command causes the app to open but nothing is filtered, and then crashes shortly after.*/
private void doSearch(String qq) {
CharSequence query = qq.toUpperCase(Locale.getDefault());
MainActivity.this.adapter.getFilter().filter(query);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
// […]
MenuItem searchItem = menu.findItem(R.id.menu_search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName());
searchView.setSearchableInfo(searchableInfo);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String qq) {
doSearch(qq);
return false;
}
@Override
public boolean onQueryTextChange(String qq) {
doSearch(qq);
return false;
}
});
return super.onCreateOptionsMenu(menu);
}
}
Adapter class
// […]
public class CustomListAdapter extends BaseAdapter {
private Activity activity;
private LayoutInflater inflater;
private List<Item> items, default_items;
private Filter myFilter;
ImageLoader imageLoader = AppController.getInstance().getImageLoader();
public CustomListAdapter(Activity activity, List<Item> items) {
this.activity = activity;
this.items = items;
this.default_items = items;
}
//[…]
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//…
}
public Filter getFilter() {
if (myFilter == null) { myFilter = new MYFilter();}
return myFilter;
}
private class MYFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence query) {
FilterResults results = new FilterResults();
// If no filter implemented, return the whole list
if (query == null || query.length() == 0) {
results.values = default_ items;
results.count = default_ items.size();
}
else {
List<Item> nitems = new ArrayList<Item>();
items = default_nitems;
for (Item t : items) {
// Filter logic implemented here, items are added to nitems if it meets conditions
}
results.values = nitems;
results.count = nitems.size();
}
return results;
}
@Override
protected void publishResults(CharSequence query, FilterResults results) {
if (results.count == 0){
items = (List<Item>) results.values;
MainActivity.emptyView.setVisibility(View.VISIBLE);
MainActivity.listView.setVisibility(View.GONE);
}
else {
items = (List< Item >) results.values;
MainActivity.listView.setVisibility(View.VISIBLE);
MainActivity.emptyView.setVisibility(View.GONE);
notifyDataSetChanged();
}
}
}
}
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application
android:name=".app.AppController"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" android:allowTaskReparenting="true">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:exported="true"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<intent-filter>
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<meta-data
android:name="android.app.default_searchable"
android:value=".MainActivity" />
</activity>
<!-- ... -->
</application>
</manifest>
Any assistance to fix this problem is appreciated.