Android search with Fragments

93,065

Solution 1

In short, you can't. There are a couple of reasons why creating a search interface within a Fragment is not possible.

  1. When creating a searchable interface, you must specify a default "searchable activity" in your Android manifest. As I'm sure you know, a Fragment cannot exist without a parent Activity and thus, this separation is not possible.

  2. If you already figured out #1 already, I assume you asked this question in hopes that there is some magical "hack" out there that can get the job done. However, the documentation states that,

    When the user executes a search in the search dialog or widget, the system starts your searchable activity and delivers it the search query in an Intent with the ACTION_SEARCH action. Your searchable activity retrieves the query from the intent's QUERY extra, then searches your data and presents the results.

    The underlying, internal system that is responsible for providing search results expects an Activity, not a Fragment; thus, implementing a search interface that is completely independent of an Activity is not possible, as it would require changes to the underlying system itself. Check out the source code for the SearchableInfo class if you don't believe me :).

That being said, it doesn't seem like it would be too difficult to achieve something similar to what you are describing. For instance, you might consider implementing your searchable-Activity so that it will accept the android.intent.action.SEARCH intent and (instead of immediately displaying the results in a ListView, for example) will pass the search query to your Fragments. For instance, consider the following searchable Activity:

public class SearchableActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
          String query = intent.getStringExtra(SearchManager.QUERY);
          doMySearch(query);
        }
    }

    /**
     * Performs a search and passes the results to the container
     * Activity that holds your Fragments.
     */
    public void doMySearch(String query) {
        // TODO: implement this
    }
}

When a search-request is made, the system will launch your searchable activity, perform the query, and will pass the results to some container Activity (based on your implementation of doMySearch). The container Activity will then pass these results to the contained searchable Fragment, in which the results will be displayed. The implementation requires a bit more work than what you were probably hoping for, but I'm sure there are ways that you can make it more modular, and it seems like this might be the best that you can do.

p.s. If you use this approach, you might have to pay special attention to which Activitys are added/removed to the backstack. See this post for some more information on how this might be done.

p.p.s. You might also forget about the standard search interface completely and just implement a simple search within a Fragment as described in Raghav's post below.

Solution 2

Here is the example to search something using fragments. Hope it helps and this is what you are looking for:

public class LoaderCursor extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        FragmentManager fm = getFragmentManager();

        // Create the list fragment and add it as our sole content.
        if (fm.findFragmentById(android.R.id.content) == null) {
            CursorLoaderListFragment list = new CursorLoaderListFragment();
            fm.beginTransaction().add(android.R.id.content, list).commit();
        }
    }

    public static class CursorLoaderListFragment extends ListFragment
            implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

        // This is the Adapter being used to display the list's data.
        SimpleCursorAdapter mAdapter;

        // If non-null, this is the current filter the user has provided.
        String mCurFilter;

        @Override public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);

            // Give some text to display if there is no data.  In a real
            // application this would come from a resource.
            setEmptyText("No phone numbers");

            // We have a menu item to show in action bar.
            setHasOptionsMenu(true);

            // Create an empty adapter we will use to display the loaded data.
            mAdapter = new SimpleCursorAdapter(getActivity(),
                    android.R.layout.simple_list_item_2, null,
                    new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
                    new int[] { android.R.id.text1, android.R.id.text2 }, 0);
            setListAdapter(mAdapter);

            // Start out with a progress indicator.
            setListShown(false);

            // Prepare the loader.  Either re-connect with an existing one,
            // or start a new one.
            getLoaderManager().initLoader(0, null, this);
        }

        @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
            // Place an action bar item for searching.
            MenuItem item = menu.add("Search");
            item.setIcon(android.R.drawable.ic_menu_search);
            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
            SearchView sv = new SearchView(getActivity());
            sv.setOnQueryTextListener(this);
            item.setActionView(sv);
        }

        public boolean onQueryTextChange(String newText) {
            // Called when the action bar search text has changed.  Update
            // the search filter, and restart the loader to do a new query
            // with this filter.
            mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
            getLoaderManager().restartLoader(0, null, this);
            return true;
        }

        @Override public boolean onQueryTextSubmit(String query) {
            // Don't care about this.
            return true;
        }

        @Override public void onListItemClick(ListView l, View v, int position, long id) {
            // Insert desired behavior here.
            Log.i("FragmentComplexList", "Item clicked: " + id);
        }

        // These are the Contacts rows that we will retrieve.
        static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
            Contacts._ID,
            Contacts.DISPLAY_NAME,
            Contacts.CONTACT_STATUS,
            Contacts.CONTACT_PRESENCE,
            Contacts.PHOTO_ID,
            Contacts.LOOKUP_KEY,
        };

        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            // This is called when a new Loader needs to be created.  This
            // sample only has one Loader, so we don't care about the ID.
            // First, pick the base URI to use depending on whether we are
            // currently filtering.
            Uri baseUri;
            if (mCurFilter != null) {
                baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                        Uri.encode(mCurFilter));
            } else {
                baseUri = Contacts.CONTENT_URI;
            }

            // Now create and return a CursorLoader that will take care of
            // creating a Cursor for the data being displayed.
            String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                    + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                    + Contacts.DISPLAY_NAME + " != '' ))";
            return new CursorLoader(getActivity(), baseUri,
                    CONTACTS_SUMMARY_PROJECTION, select, null,
                    Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
        }

        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            // Swap the new cursor in.  (The framework will take care of closing the
            // old cursor once we return.)
            mAdapter.swapCursor(data);

            // The list should now be shown.
            if (isResumed()) {
                setListShown(true);
            } else {
                setListShownNoAnimation(true);
            }
        }

        public void onLoaderReset(Loader<Cursor> loader) {
            // This is called when the last Cursor provided to onLoadFinished()
            // above is about to be closed.  We need to make sure we are no
            // longer using it.
            mAdapter.swapCursor(null);
        }
    }
}

Solution 3

It is quite possible to search in a fragment using the standard ActionBar SearchView ActionView API. This will work back to Android 2.1 (API level 7) too using AppCompat support classes v7.

In your fragment:

@Override
public void onCreateOptionsMenu (Menu menu, MenuInflater inflater){
    inflater.inflate(R.menu.search, menu);
    MenuItem item = menu.findItem(R.id.action_search);
    SearchView sv = new SearchView(((YourActivity) getActivity()).getSupportActionBar().getThemedContext());
    MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
    MenuItemCompat.setActionView(item, sv);
    sv.setOnQueryTextListener(new OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            System.out.println("search query submit");
            return false;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            System.out.println("tap");
            return false;
        }
    });
}

In your menu XML

<item
    android:id="@+id/action_search"
    android:icon="@drawable/ic_action_search"
    android:title="Search Waste Items"
    android:showAsAction="ifRoom|collapseActionView"
    nz.govt.app:actionViewClass="android.support.v7.widget.SearchView"
    nz.govt.app:showAsAction="ifRoom|collapseActionView" />

Solution 4

Using AppCompat support classes v7. Just adding something to @David 's solution from @Rookie solution to get it work properly in a simple manner, here is my fragment code:

MyFragment:

public class MyFragment extends Fragment implements SearchView.OnQueryTextListener {

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // What i have added is this
        setHasOptionsMenu(true);
    }

    @Override
    public void onCreateOptionsMenu (Menu menu, MenuInflater inflater) {

        //inflater.inflate(R.menu.main, menu); // removed to not double the menu items
        MenuItem item = menu.findItem(R.id.action_search);
        SearchView sv = new SearchView(((MainActivity) getActivity()).getSupportActionBar().getThemedContext());
        MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
        MenuItemCompat.setActionView(item, sv);
        sv.setOnQueryTextListener(this);
        sv.setIconifiedByDefault(false);
        sv.setOnSearchClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Utils.LogDebug("Clicked: ");
            }
        });

        MenuItemCompat.setOnActionExpandListener(item, new MenuItemCompat.OnActionExpandListener() {
            @Override
            public boolean onMenuItemActionCollapse(MenuItem item) {
                // Do something when collapsed
                Utils.LogDebug("Closed: ");
                return true;  // Return true to collapse action view
            }

            @Override
            public boolean onMenuItemActionExpand(MenuItem item) {
                // Do something when expanded
                Utils.LogDebug("Openeed: ");
                return true;  // Return true to expand action view
            }
        });

        super.onCreateOptionsMenu(menu,inflater);
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        Utils.LogDebug("Submitted: "+query);
        return true;
    }

    @Override
    public boolean onQueryTextChange(String newText) {
        Utils.LogDebug("Changed: "+newText);
        return false;
    }
}

I added the onActivityCreated, cuz without calling setHasOptionsMenu(true); the system will not know that this fragment needs to interact with the menu.

then I removed the line inflater.inflate(R.menu.main, menu); because it doubled the menu items since Activity inflated a menu, then Fragment inflated another menu

Thanks to @David and @Rookie

Solution 5

When working with Fragments you still need to use an Activity to control and assign the Fragments. This Activity can have search functionality as before.

I've recently switched from a 'normal' Activity based app, to a Fragment based app and the search functionality worked just the same for me.

Have you tried working on it, and didn't succeed? If so give some more detail in your question.

EDIT:

If you want to have a fragment specific search, have all your Fragments extend an interface MyFragment with a startSearch method, and have your Activity's startSearch method call the current fragment's startSearch method.

Share:
93,065

Related videos on Youtube

Blackbelt
Author by

Blackbelt

Updated on March 10, 2020

Comments

  • Blackbelt
    Blackbelt about 4 years

    Does somebody know of a tutorial or an example of how to implement the standard Android search interface with Fragments? In other words, is it possible to put a standard search with a SearchManager in a Fragment?

    • Snicolas
      Snicolas about 12 years
      Who would you grant the bonus to @blackbelt ?Raghav gave the answer I was looking for. but Alex LockWood also answered to the first part of your question.
    • Blackbelt
      Blackbelt about 12 years
      I accept LockWood's answer. Award the bounty as you like (I think is better)
    • Alex Lockwood
      Alex Lockwood about 12 years
      I'm glad we ended up getting such a variety of correct answers and workarounds! :)
    • Rookie
      Rookie almost 12 years
      Can anyone give me the answer to this question..? I am stuck here stackoverflow.com/questions/10600660/…
    • span
      span almost 12 years
      I'm using this technique to stay within the fragment after adding the SearchView to the actionbar: stackoverflow.com/a/6939735/1068167 . My application is not done yet but hopefully it will work.
    • Vinay S Shenoy
      Vinay S Shenoy over 11 years
      I have successfully implemented Search in fragments using the Android Search Interface(SearchView in Action Bar) with custom suggestions and displaying of results. Each fragment loads its own suggestions dynamically from the network(cached into a local DB). It took a lot of work, but it works very well. Add a comment if you would like me to give my answer because it will take a lot of explanation.. :-)
    • Blackbelt
      Blackbelt over 11 years
      go ahead. could be useful for other people. thanks
  • Blackbelt
    Blackbelt about 12 years
    Requirement was that the android search was bounded to the specific fragment, not to the activity. So, no I had not tried it.
  • Alex Lockwood
    Alex Lockwood about 12 years
    The OP asked for "standard search interface", but this does achieve a simple "search" within a Fragment so I wouldn't complain. Congrats! :P
  • Adrian Monk
    Adrian Monk about 12 years
    Next time you might want to cite your sources.
  • Alex Lockwood
    Alex Lockwood about 12 years
    I'd also like to point out that Jake Wharton decided not to implement the SearchView for his ActionBarSherlock library due to its complexity (see his comment here). As you can see (and as I described in my answer as well), Android's search implementation is anything but trivial... it is ingrained in the underlying system, and this makes even 3rd party libraries difficult to write (and that's saying something... Jake Wharton is like the Chuck Norris of 3rd party libraries! :D).
  • Alex Lockwood
    Alex Lockwood about 12 years
    Take a look at the SearchView.java source code here.
  • Alex Lockwood
    Alex Lockwood about 12 years
    See creating a search interface on the developer's site. The OP asked if it was possible to create a "searchable-Fragment" that works with the standard Android search as described in the documentation.
  • Jose_GD
    Jose_GD almost 12 years
    Alex, it seems Jake Wharton changed his mind: twitter.com/JakeWharton/status/221169921235755009
  • Vinay S Shenoy
    Vinay S Shenoy over 11 years
    @Alex, It's definitely possible to implement the standard Android search interface using fragments. But it takes a LOT of work... :-)
  • Alex Lockwood
    Alex Lockwood over 11 years
    @VinaySShenoy yeah, I mean you'd have to re-implement the entire SearchManager for Fragments right? (or something like that)
  • Vinay S Shenoy
    Vinay S Shenoy over 11 years
    For delivering Search Results, I've implemented something similar to what you suggested, delivering the intent to the Activity which delivers it to the right fragment if it's visible. The toughest part was repopulating the Suggestions Table on fragment load with the right data to let me handle displaying suggestions as well as to handle search submit and clicking on suggestions. But I've got a nice framework in place for future apps now.. :-)
  • AlikElzin-kilaka
    AlikElzin-kilaka about 11 years
    Some pitfalls: 1-Using a customized search icon doesn't work. 2-SearchView needs to have some management code, like for disabling the content, handling back, etc.
  • Kyle Falconer
    Kyle Falconer almost 11 years
    I found this to be a better answer: stackoverflow.com/questions/6938952/… This will allow "searching" from within Fragments. True, it's not the official search mechanism provided by Google, but it works just fine.
  • Pelanes
    Pelanes about 10 years
    This works great with the new ActionBarActivity appcompat v7, thanks
  • Clocker
    Clocker almost 10 years
    Are both of these xml files the same?
  • Snicolas
    Snicolas almost 10 years
    @Clocker, looks like they are. Honestly, after 2 years I can't say
  • ImAtWar
    ImAtWar over 9 years
    Would you need a Search Manager?
  • Shajeel Afzal
    Shajeel Afzal almost 9 years
    In my case onQueryTextChange method is not being called.
  • MBH
    MBH over 8 years
    reading the accepted answer, i lost hope..the other answers are complex, i read your answer!!! So simple! thats it...thank you so much
  • hornet2319
    hornet2319 over 8 years
    that works well, thanks! Do you have any advices about Recent Query Suggestions implementation?
  • Raymond Lukanta
    Raymond Lukanta over 8 years
    I think we have to add setHasOptionsMenu(true) in onActivityCreated. As spotted by @MBH.
  • MorZa
    MorZa over 8 years
    @RaymondLukanta is right - but I added setHasOptionsMenu(true) in the fragment's onCreate method.
  • deadfish
    deadfish over 8 years
    am I the only one who gets doubled result from onQuertTextSubmit?
  • klimat
    klimat almost 8 years
    It still depends on Activity. You have to set theme which supports ActionBar on it.
  • Gowthaman M
    Gowthaman M over 6 years
    MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); MenuItemCompat.setActionView(item, sv); this are the lines are deprecated please help me in this case bro
  • Bassinator
    Bassinator over 6 years
    I haven't tried it yet, but this sounds like EXACTLY what I'm looking for. I have a navigation drawer with fragments that each contain unrelated search features. I don't want to bypass the android search system entirely, so I needed something like this.
  • David
    David over 6 years
    @GowthamanM Just use MenuItem.SHOW_AS_ACTION.... if you are able the AppCompat is deprecated on later APIs
  • Rod Lima
    Rod Lima over 5 years
    Hey @GowthamanM, use like this: item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION‌​_VIEW | MenuItem.SHOW_AS_ACTION_IF_ROOM); item.setActionView(searchView);