Search code examples
androidandroid-contentprovidersearchviewfragmentpageradapter

Changing search function in activity depending on active fragment


I have an activity with a TabLayout that's hooked up to a FragmentPagerAdapter. Whenever the user changes tabs, another fragment is loaded, but the Activity stays the same. The activity also contains a search function, i.e. autocomplete suggestions are fetched from a web server. I want to perform a different search action - basically, query another URL - depending on which fragment is currently loaded in the FragmentPager/TabLayout. So far, I haven't found any way to do this. Here's what I have tried:

  • currently, the search function is connected to a content provider (via manifest and searchable.xml). However, I haven't found a way to pass data to this ContentProvider to let it know which tab is selected
  • using multiple content providers doesn't seem to be possible because there can only be one for any given Activity, and the Activity isn't supposed to change when selecting another tab
  • manipulating the Activity's onQueryText* methods isn't feasible because I don't want to block the UI thread fetching the data

Here's a quick sketch of the situation: Sketch

Here are the relevant code fragments:

MainActivity.class

public class MainActivity extends AppCompatActivity {
    private ViewPager mViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mViewPager = (ViewPager) findViewById(R.id.viewpager);
        mViewPager.setAdapter(new MainFragmentPagerAdapter(getSupportFragmentManager(),
                MainActivity.this));

        TabLayout tabs = (TabLayout) findViewById(R.id.sliding_tabs);
        tabs.setupWithViewPager(mViewPager);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.action_search, menu);


        MenuItem searchItem = menu.findItem(R.id.action_search);
        SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
        searchView.setOnQueryTextListener(this);

        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, MainActivity.class)));
        searchView.setIconifiedByDefault(false);

        return true;
    }

    /* ... */
}

menu/action_search.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/action_search"
        android:title="Search"
        android:icon="@android:drawable/ic_menu_search"
        app:showAsAction="always"
        app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>

xml/searchable.xml

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:hint="hint"
    android:label="@string/app_name"
    android:voiceSearchMode="showVoiceSearchButton|launchRecognizer"
    android:searchSuggestAuthority="xyz.demo.search.datasuggestion"
    android:searchSuggestIntentAction="android.intent.action.VIEW"
    android:searchSuggestIntentData="content://xyz.demo.search.data"/>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xyz.demo.app">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <meta-data
            android:name="android.app.default_searchable"
            android:value=".MainActivity" />
        <activity android:name=".MainActivity" android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.intent.action.SEARCH" />
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable" />
        </activity>

        <provider
            android:name=".SuggestionProvider"
            android:authorities="xyz.demo.search.datasuggestion"
            android:enabled="true"
            android:exported="true"/>
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest>

SuggestionProvider.java

public class SuggestionProvider extends ContentProvider {
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {

          OkHttpClient client = new OkHttpClient();
          Request request = new Request.Builder()
                  .url(".../suggestions")
                  .build();

          try {
              Response response = client.newCall(request).execute();
              String jsonString = response.body().string();
              JSONArray jsonArray = new JSONArray(jsonString);

              /* ... */

          } catch (Exception e) {
              e.printStackTrace();
          }

          /* ... eventually returns a MatrixCursor */
    }

    /* ... */
}

Solution

  • Ah okay I see this is one of those things where Android is calling your provider and you can't control how it's going to call it.

    I don't see a way to do this cleanly either. The searchable is linked to the activity. You either base your screens on the activity not the fragment (changing your design), or do something terrible like set a preference when your fragment resumes so the content provider knows which fragment is "active".