hornet2319
hornet2319

Reputation: 339

Recent Query Suggestions in Fragment

I implemented SearchView in Fragment as @David suggested in this topic. Does anyone know how to add Recent Query Suggestions to SearchView in Fragment?

Upvotes: 3

Views: 2519

Answers (2)

iamsr
iamsr

Reputation: 412

Ok so I found this solution with the help of this particular repository on github thanks and credit to him.

https://github.com/nex3z/android-examples/tree/master/CustomSearchSuggestionItem

Here is a sample implementation of how you can integrate a search view in fragment with recent suggestion query in Kotlin -

  1. Create Adapter For Showing Recent List

    SearchAdapter.java

     public class SearchAdapter extends CursorAdapter {
    
     private static final String LOG_TAG = 
     SearchAdapter.class.getSimpleName();
    
     public SearchAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
      }
    
     @Override
     public View newView(Context context, Cursor cursor, ViewGroup parent) {
     View view = LayoutInflater.from(context).inflate(
               R.layout.search_item, parent, false);
    
     ViewHolder viewHolder = new ViewHolder(view);
     view.setTag(viewHolder);
    
     return view;
      }
    
     @Override
     public void bindView(View view, Context context, Cursor cursor) {
       ViewHolder viewHolder = (ViewHolder) view.getTag();
       viewHolder.mTitle.setText(
       cursor.getString(cursor.
           getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1)));
         }
    
      public String getSuggestionText(int position) {
        if (position >= 0 && position < getCursor().getCount()) {
        Cursor cursor = getCursor();
        cursor.moveToPosition(position);
        return cursor.getString(cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1));
        }
        return null;
          }
    
        public static class ViewHolder {
         public TextView mTitle;
    
            public ViewHolder(View view) { 
            // view of your custom layout
            mTitle = (TextView) view.findViewById(R.id.text);
              }
           }
         }
    

    search_item.xml (Inside Layout Folder)

      <TextView xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tool="http://schemas.android.com/tools"
      android:id="@+id/text"
      android:layout_width="wrap_content"
      android:layout_height="50dp"
      android:fontFamily="@font/poppins_bold"
      android:textColor="@color/colorPrimary"
      android:gravity="start|center"
      android:paddingStart="30dp"
      android:paddingEnd="30dp"
      tool:text="hello" />
    

    menu.xml (put this inside your menu.xml file)

    <item
    android:id="@+id/action_search"
    android:icon="@android:drawable/ic_menu_search"
    android:title="@string/search"
    app:actionViewClass="androidx.appcompat.widget.SearchView"
    app:showAsAction="always" />
    

    SuggestionProvider.kt

     class SuggestionProvider : SearchRecentSuggestionsProvider() {
       init {
        setupSuggestions(AUTHORITY, MODE)
       }
    
       companion object {
        // Set As Path To This File
        const val AUTHORITY = "com.your_package_name.SuggestionProvider"
        const val MODE: Int = DATABASE_MODE_QUERIES
       }
     }
    

    AndroidManifest.xml (Make Sure android:authrotities field have same path which is on SuggestionProvider.kt AUTHORITY FIELD HAVE)

      <application>
       .....
        <provider
        android:name=".utils.SuggestionProvider"
        android:authorities="com.you_package_name.SuggestionProvider" />
    
       .....
       </application>
    

    YourFragment.kt (Put this code inside your already created fragment where you want to have search bar in action bar)

    private var mSuggestionAdapter: SearchAdapter? = null
    private lateinit var searchView: SearchView
    private var queryTextListener: SearchView.OnQueryTextListener? = null
    
    
    
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
    when (item.itemId) {
        R.id.action_search ->
            return false
    }
    searchView.setOnQueryTextListener(queryTextListener)
    return super.onOptionsItemSelected(item)
    }
    
    
    
    
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
    inflater.inflate(R.menu.menu, menu)
    val searchItem = menu.findItem(R.id.action_search)
    val searchManager =
        activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager
    
    mSuggestionAdapter = SearchAdapter(activity, null, 0)
    
    if (searchItem != null) {
        searchView = searchItem.actionView as SearchView
    }
    if (searchView != null) {
        searchView.setSearchableInfo(searchManager.getSearchableInfo(activity!!.componentName))
        searchView.suggestionsAdapter = mSuggestionAdapter;
    
        queryTextListener = object : SearchView.OnQueryTextListener {
            override fun onQueryTextChange(newText: String): Boolean {
                // Update Cursor With Each Query Text Change
                val cursor = getRecentSuggestions(newText)
                if (cursor != null) {
                    mSuggestionAdapter?.swapCursor(cursor)
                }
                return false
            }
    
            override fun onQueryTextSubmit(query: String): Boolean {
    
                // Save Submitted Query To Adapter
                val suggestions = SearchRecentSuggestions(
                    activity,
                    SuggestionProvider.AUTHORITY, SuggestionProvider.MODE
                )
                suggestions.saveRecentQuery(query, null)
    
                // Do Your Search Stuff Here With Query
                return true
            }
        }
    
        searchView.setOnSuggestionListener(object : SearchView.OnSuggestionListener {
            override fun onSuggestionSelect(position: Int): Boolean {
                return false
            }
    
            override fun onSuggestionClick(position: Int): Boolean {
            // On Clicking Suggestion Load It To Submit Query Listener    
    
          searchView.setQuery(mSuggestionAdapter?.getSuggestionText(position), true)
                return true
            }
        })
    
    
        searchView.setOnQueryTextListener(queryTextListener)
    
         }
         super.onCreateOptionsMenu(menu, inflater)
        }
    
    
        // Function To Retrieve Suggestion From Content Resolver
        fun getRecentSuggestions(query: String): Cursor? {
         val uriBuilder = Uri.Builder()
        .scheme(ContentResolver.SCHEME_CONTENT)
        .authority(SuggestionProvider.AUTHORITY)
    
        uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY)
    
         val selection = " ?"
         val selArgs = arrayOf(query)
    
         val uri = uriBuilder.build()
        return activity?.contentResolver?.query(uri, null, selection, selArgs, null)
         }
    

That's It you have a search bar in your fragment which query result you can control how you handle searching and a recent suggestion.

Upvotes: 3

Soon Santos
Soon Santos

Reputation: 2219

From android documentation.

STEP 1 - Create a Content Provider

Creathe MySuggestionProvider class, mine is located at searchviewpager/utils/MySuggestionProvider

package com.soon.karat.searchviewpager.utils;

import android.content.SearchRecentSuggestionsProvider;

public class MySuggestionProvider extends SearchRecentSuggestionsProvider {
    // AUTHORITY is a unique name, but it is recommended to use the name of the 
    // package followed by the name of the class.
    public final static String AUTHORITY = "com.soon.karat.searchviewpager.utils.MySuggestionProvider";

    // Uncomment line below, if you want to provide two lines in each suggestion:
    // public final static int MODE = DATABASE_MODE_QUERIES | DATABASE_MODE_2LINES;
    public final static int MODE = DATABASE_MODE_QUERIES;

    public MySuggestionProvider() {
        setupSuggestions(AUTHORITY, MODE);
    }
}

STEP 2 - Add the provider into the Manifest

Add the following line of code in your Manifest in the application level

<application...>
        <provider
            android:name=".utils.MySuggestionProvider"
            android:authorities="com.soon.karat.searchviewpager.utils.MySuggestionProvider" />

android:name: It is the path where your SuggestionProvider class is located. Mine is located at searchviewpager/utils/MySuggestionProvider --> therefore the name will be .utils.MySuggestionProvider

android:authorities: It is the same String AUTHORITY you put in your SuggestionProvider, It should be the same name.

STEP 3 - Add search to your searchable.xml

Add the two last lines (searchSuggestAuthority and searchSuggestSelection) into your searchable.xml

<?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_for_anything"
    android:searchSuggestAuthority="com.soon.karat.searchviewpager.utils.MySuggestionProvider"
    android:searchSuggestSelection=" ?"/>

android:searchSuggestAuthority: It is the same String AUTHORITY you put in your MySuggestionProvider class.

android:searchSuggestSelection: It is just a space and a question marker.

STEP 4 - Save the Queries

Whenever the user submit a query you should save it in your SuggestionProvider, let's say you will save it inside onQueryTextSubmit

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            Log.i(TAG, "onQueryTextSubmit: Query was submitted");

            // -------------------------------------------------------
            //           Lines responsible to save the query 
            // -------------------------------------------------------
            SearchRecentSuggestions suggestions = new SearchRecentSuggestions(MainActivity.this,
                    MySuggestionProvider.AUTHORITY,
                    MySuggestionProvider.MODE);
            suggestions.saveRecentQuery(query, null);
            // -------------------------------------------------------

            displaySearchResults(query);

            // The listener can override the standard behavior by returning true to indicate that it has
            // handled the submit request. Otherwise return false to let the SearchView handle the
            // submission by launching any associated intent.
            return true;
        }

ADITIONALS

Hide the Keyboard and Dismiss the SearchView

Maybe you want to hide the keyboard and dismiss the searchview when the user finish submitting the query to your app. In order to do that you use the following lines of code.

private void dismissKeyBoardAndSearchView() {
    Helpers.hideKeyBoard(this);
    // You should check if MenuItem is not null, otherwise the app would crash when rotating the screen 
    if (searchMenuItem != null) {
        searchMenuItem.collapseActionView();
    }
}

searchMenuItem is a MenuItem and it is the same you used when creating the SearchView in onCreateOptionsMenu, you just declare it globally to access in this other method or you parse it into the method:

@Override
public boolean onCreateOptionsMenu(Menu menu) {

    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.options_menu, menu);

    SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    // -----------------------------------------------
    //     This is the MenuItem you will collapse
    // -----------------------------------------------
    searchMenuItem = menu.findItem(R.id.menu_search);
    // -----------------------------------------------
    SearchView searchView = (SearchView) searchMenuItem.getActionView();
    searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
    searchView.setQueryRefinementEnabled(true);

    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {

Hiding the keyboard is inside Helpers Class

public class Helpers {

public static void hideKeyBoard(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    View view = activity.getCurrentFocus();
    if (view == null) {
        view = new View(activity);
    }
    assert inputMethodManager != null;
    inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}

}

Handle when the user clicks in a suggestion

You may notice that when the user clicks in a suggestion nothing will happen because onQueryTextSubmit is not called. Android just restart the activity and send the query to it. Then, to handle clicks in the suggestion you should add the following code:

Supposing the activity that make the search is the same that receives the search:

STEP 1 - Set launchMode to singleTop

Set your activity launchMode to singleTop in the Manifest to prevent the system to recreate the activity several times when the user makes several searches.

<application...>
    <provider
        android:name=".utils.MySuggestionProvider"
        android:authorities="com.soon.karat.searchviewpager.utils.MySuggestionProvider" />
    <activity android:name=".SearchableActivity"
        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>

STEP 2 - Handle the intent in onCreate and in onNewIntent

You should handle the search intent both in onCreate and in onNewItent. onNewIntent is necessary because you set your activity to singleTop, when the app receives a search, instead of recreating the activity and calling onCreate, it will just call onNewIntent.

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

    handleIntent(getIntent());
}


@Override
protected void onNewIntent(Intent intent) {
    setIntent(intent);
    handleIntent(intent);
}

Customizing your recent query suggestion layout

Maybe, you do not like the way your recent query suggestion are being displayed, then you can change its layout by a simple way.

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>

    <!-- Add this line -->
    <item name="searchViewStyle">@style/MySearchViewStyle</item>
</style>

<style name="MySearchViewStyle" parent="Widget.AppCompat.SearchView" >
    <item name="suggestionRowLayout">@layout/my_search_dropdown_item_icons</item>
</style>

my_search_drodown_item_icons: This is the layout you create and customize it.

WARNING: You should keep with the same ids android had for the previous layout in order for it to work. Then, go to this file @layout/abc_search_dropdown_item_icons_2line and copy the same ids.

If you have difficult to found this abc file, you can replace @layout/my_search_dropdown_item_icons to --> @layout/abc_search_dropdown_item_icons_2line --> then place your cursor into "abc_search..." and press Ctrl+B. You will be redirect to that file where you can take the same ids.

Upvotes: 6

Related Questions