Android: Providing recent search suggestions without searchable activity?

android, java, searchview

Solution

Ok, I spent my time for this. I make my own simple suggestion implementation from `SQLiteDatabase`.

We will create 3 classes like the following

- `MainActivity` - for test of `SearchView` suggestion from database

- `SuggestionDatabase` - this will store your recent search keyword.

- `SuggestionSimpleCursorAdapter` - this is a subclass of `SimpleCursorAdapter`. I'll explain why I make this class instead of using `SimpleCursorAdapter`.

The codes

// MainActivity.java

public class MainActivity 
    extends Activity
    implements SearchView.OnQueryTextListener,
                SearchView.OnSuggestionListener
{

    private SuggestionsDatabase database;
    private SearchView searchView;

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


        database = new SuggestionsDatabase(this);
        searchView = (SearchView) findViewById(R.id.searchView1);
        searchView.setOnQueryTextListener(this); 
        searchView.setOnSuggestionListener(this);
    }

    @Override
    public boolean onSuggestionSelect(int position) {

        return false;
    }

    @Override
    public boolean onSuggestionClick(int position) {

        SQLiteCursor cursor = (SQLiteCursor) searchView.getSuggestionsAdapter().getItem(position);
        int indexColumnSuggestion = cursor.getColumnIndex( SuggestionsDatabase.FIELD_SUGGESTION);

        searchView.setQuery(cursor.getString(indexColumnSuggestion), false);

        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        long result = database.insertSuggestion(query);
        return result != -1;
    }

    @Override
    public boolean onQueryTextChange(String newText) {

        Cursor cursor = database.getSuggestions(newText);
        if(cursor.getCount() != 0)
        {
            String[] columns = new String[] {SuggestionsDatabase.FIELD_SUGGESTION };
            int[] columnTextId = new int[] { android.R.id.text1};

            SuggestionSimpleCursorAdapter simple = new SuggestionSimpleCursorAdapter(getBaseContext(),
                    android.R.layout.simple_list_item_1, cursor,
                    columns , columnTextId
                    , 0);

            searchView.setSuggestionsAdapter(simple);
            return true;
        }
        else
        {
            return false;
        }
    }

}

How it works

- When user taps the search button, the `onQueryTextSubmit()` will be triggered and then the search keyword will be saved in our database. Let's say we submit a keyword "Hello"

- If the user writes a string for example "Hel" or "H" in `SearchView` the `onQueryTextChange()` will be called and then we search this keyword in `SQLiteDatabase` (`SuggestionDatabase`). If "Hel" or "H" matches "Hello" , display the results of query by setting the returned Cursor in `SuggestionSimpleCursorAdapter` and then set this adapter in `SearchView`. Here's the picture.

3. Ofcourse we will tap the suggestion which is "Hello", `onSuggestionClick(int position)` will be called for that. We get the `SQLiteCursor` object from the `SearchView`'s adapter (`SuggestionSimpleCursorAdapter`) and get the Suggestion text from it, set the suggestion text in `SearchView` object

SQLiteCursor cursor = (SQLiteCursor) searchView.getSuggestionsAdapter().getItem(position);
int indexColumnSuggestion = cursor.getColumnIndex( SuggestionsDatabase.FIELD_SUGGESTION);
searchView.setQuery(cursor.getString(indexColumnSuggestion), false);

If we use `SimpleCursorAdapter` it also works properly but let's say we have this scenario

- If We run this program in smartphone and type the keyword "Hel", the suggestion will appear properly.

- What if We rotate the screen in landscape? It will switch in Full Screen mode and you can still type the keyword.

What will happen in suggestion? Let's take a look.

See the weird suggestion? How we solve that? By overriding the `convertToString(Cursor cursor)` which returns a `CharSequence`

 // SuggestionSimpleCursorAdapter.java
public class SuggestionSimpleCursorAdapter
    extends SimpleCursorAdapter
{

    public SuggestionSimpleCursorAdapter(Context context, int layout, Cursor c,
            String[] from, int[] to) {
        super(context, layout, c, from, to);
    }

    public SuggestionSimpleCursorAdapter(Context context, int layout, Cursor c,
            String[] from, int[] to, int flags) {
        super(context, layout, c, from, to, flags);
    }

    @Override
    public CharSequence convertToString(Cursor cursor) {

        int indexColumnSuggestion = cursor.getColumnIndex(SuggestionsDatabase.FIELD_SUGGESTION);

        return cursor.getString(indexColumnSuggestion);
    }


}

By overriding `convertToString(Cursor cursor)` , here's the result

And here's the database

// SuggestionDatabase.java
public class SuggestionsDatabase {

  public static final String DB_SUGGESTION = "SUGGESTION_DB";
  public final static String TABLE_SUGGESTION = "SUGGESTION_TB";
  public final static String FIELD_ID = "_id";
  public final static String FIELD_SUGGESTION = "suggestion";

  private SQLiteDatabase db;
  private Helper helper;

  public SuggestionsDatabase(Context context) {

    helper = new Helper(context, DB_SUGGESTION, null, 1);
    db = helper.getWritableDatabase();
  }

  public long insertSuggestion(String text)
  {
    ContentValues values = new ContentValues();
    values.put(FIELD_SUGGESTION, text);
    return db.insert(TABLE_SUGGESTION, null, values);
  }

  public Cursor getSuggestions(String text)
  {
    return db.query(TABLE_SUGGESTION, new String[] {FIELD_ID, FIELD_SUGGESTION}, 
            FIELD_SUGGESTION+" LIKE '"+ text +"%'", null, null, null, null);
  }


    private class Helper extends SQLiteOpenHelper
    {

    public Helper(Context context, String name, CursorFactory factory,
            int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE "+TABLE_SUGGESTION+" ("+
                    FIELD_ID+" integer primary key autoincrement, "+FIELD_SUGGESTION+" text);");
        Log.d("SUGGESTION", "DB CREATED");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

  }

}

I hope this answer is useful to other programmers alot. :)

Problem

I have an ActionBar SearchView and I am successfully able to make searches with it. The android documentation does not explain how to implement search suggestions. I do not want to have a searchable activity. This is my search code: ``` public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_add_song, menu); final SearchView searchView = (SearchView) menu.findItem(R.id.song_search).getActionView(); searchView.setFocusable(true); searchView.setIconified(false); final AddSongActivity activity = this; searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextChange(String newText) { // Do nothing return true; } @Override public boolean onQueryTextSubmit(String query) { // Clear SearchView searchView.clearFocus(); // Begin Spotify Search TextView notice = (TextView)findViewById(R.id.search_notice); URL url; try { url = new URL("http://ws.spotify.com/search/1/track.json?q=" + URLEncoder.encode(query,"UTF-8")); } catch (MalformedURLException e) { notice.setText("Malformed Search"); notice.setHeight(noticeHeight); return true; } catch (UnsupportedEncodingException e) { notice.setText("Unsupported Encoding. Maybe a problem with your device."); notice.setHeight(noticeHeight); return true; } new SearchDownload(url, activity).execute(); notice.setText("Loading Tracks"); notice.setHeight(noticeHeight); Log.i("infodb","" + noticeHeight); return true; } }); ``` This works for searching but I have no idea to implement recent search query suggestions. How do I go about doing this? Thank you.

Original source