Android how to add Search Widget and SearchView Implementation in Android UI

In this tutorial, we are going to learn how to add Search Widget in android app bar. After then, we will also implement a search android project on how to add android SearchView in android UI rather than in your App bar.

This will be a long tutorial but I do hope that at the end we will be glad to see how much we have learned.

In android, Search functionality can be implemented in different ways. I have covered how we can implement search feature in android by using EditText with Android TextWatcher class in Android Dictionary Application with search suggest

Another method I recently wrote about is using android search dialog and letting the android system handle the search process. You can find the post here – Android Search Dialog Implementation with SQLite Database

You can read more on how to implement search interface in android in developers guide

This tutorial will be slightly similar to the one before so if you have read the previous post, everything will be easier for you to understand.

Google suggestion is to use the Android Search Widget if you are targeting android version 3 and above.

Android Search Widget can be implemented in the App bar or part of the Activity UI widgets. We will cover how to achieve these two positioning but right now we will start with the first option.

Before we dive into more details, it is important for us to understand what we are planning to achieve. Below is the screen-shot of the application we will be creating.

android search widget

Lets start to soil our hands in code. we will create our project in our IDE. For this tutorial, I am using the following tools and environment, feel free to use what works for you.

Windows 7

Android Studio

Sony Xperia ZL

Min SDK 14

Target SDK 23

To create a new android application project, follow the steps as stipulated below.

Go to File menu

Click on New menu

Click on Android Application

Enter Project name: AndroidSearchFinctionality

Package: com.inducesmile.androidsearchfunctionality

Select Blank Activity

Name the Activity SearchableActivity

Keep other default selections

Continue to click on next button until Finish button is active, then click on Finish Button.

Go to the res folder, then double click on the values folder and double click on the colors.xml file and add the following lines of codes. This will holder all the colors we will use in this tutorial.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
    <color name="colorBlack">#000000</color>
</resources>

Now, head over to the strings.xml file and modify the content with the code below.

<resources>
    <string name="app_name">Android Search Functionality</string>
    <string name="app_label"> Search Feature</string>
    <string name="search_hint">Search Fruit</string>
    <string name="search_view">Enter search</string>
</resources>

Since we will let the android system handle the search process, we will add the Intent Action Search in the Activity element of the SearchableActivity. Additionally, we will add a meta-data with name and resource as shown

<meta-data
    android:name="android.app.searchable"
    android:resource="@xml/searchable" />

The complete code for the Mainfest.xml file is as shown below

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

The meta-data in the SearchableActivity element has name and resource. The resource is linked to searchable configuration file located in res/xml folder. The system uses this file to instantiate a SearchableInfo object, but you cannot create this object yourself at runtime—you must declare the searchable configuration in XML.

The searchable configuration file must include the <searchable> element as the root node and specify one or more attributes

Now create a folder in your res directory and name it xml. Right click on this folder, click on new file, select xml resource file and name it – searchable.xml.

Open this file in your IDE, copy and paste the code below inside the file

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_label"
    android:hint="@string/search_hint" >
</searchable>

The android:label attribute is the only required attribute. It points to a string resource, which should be the application name.

We are going to add the android search widget as a menu Action View. We will create a menu directory inside the res folder if it is not there. Thereafter, create a menu resource file and name it search_menu.xml. Copy and paste the code below inside this file.

<?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/search"
        android:title="@string/search_view"
        android:icon="@android:drawable/ic_menu_search"
        app:showAsAction="collapseActionView|ifRoom"
        app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>

Now let us move over to our SearchableActivity layout file. The Layout file – activity_searchable.xml will contain a single ListView widget. The ListView will hold the retrieved search result. The code for the activity_searchable.xml file is as shown

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.inducesmile.androidsearchfunctionality.SearchableActivity">
    <ListView
        android:id="@+id/listView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="16dp"
        android:layout_marginTop="16dp"
        android:padding="8dp"
        android:scrollbars="none" />
</RelativeLayout>

It is important to note that we have added a single line in the Mainfest.xml file

android:launchMode=“singleTop”

If you set android:launchMode to “singleTop”, then the searchable activity receives the ACTION_SEARCH intent with a call to onNewIntent(Intent), passing the new ACTION_SEARCH intent here. For example, here’s how you might handle this case, in which the searchable activity’s launch mode is “singleTop”

For the SearchableActivity class, We are going to override these two methods onCreateOptionsMenu(Menu menu) and onOptionsItemSelected(MenuItem item). When the search icon item we added in our menu list is click, we will call onSearchRequested() method which will display the android search dialog.

When the application is launch, you will see default android search icon on the app bar, if the icon is clicked, it will display the android search widget. Enter the input you wish to search and click on enter button.

The instance of the ListView is created using the Activity class findViewById() method. We have add a List that contains an Entity object which wraps row data obtained from the local SQLite database as a class member variable couple with a custom adapter and the database class.

public class SearchableActivity extends AppCompatActivity {
    private SearchAdapter mSearchAdapter;
    private ListView listView;
    private DbBackend databaseObject;

In onCreateOptionMenu(Menu menu) method of the SearchableActivity class, we inflate the search_menu.xml file we create earlier on. Subsequently, an instance of the SearchManager is obtained from Android System Srevice. The SearchView object is obtained as an actionview and the setSearchableInfo() and setIconifiedByDefault() methods of the class is called.

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.search_menu, menu);
    SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
    searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
    searchView.setIconifiedByDefault(false);
    return true;
}

You can see that we created a doMySearch(String query) method. Inside this method, we use the obtain search string to query our database. The resulting output from the database query is passed as a parameter to our SearchAdapter class.

The complete code for the SearchableActivity class is as shown

import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import com.inducesmile.androidsearchfunctionality.database.DbBackend;
import com.inducesmile.androidsearchfunctionality.database.ItemObject;
import java.util.List;
public class SearchableActivity extends AppCompatActivity {
    private SearchAdapter mSearchAdapter;
    private ListView listView;
    private DbBackend databaseObject;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_searchable);
        // create database object
        databaseObject = new DbBackend(SearchableActivity.this);
        listView = (ListView)findViewById(R.id.listView);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            }
        });
        handleIntent(getIntent());
    }
    @Override
    protected void onNewIntent(Intent intent) {
        setIntent(intent);
        handleIntent(intent);
    }
    private void handleIntent(Intent intent) {
        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
            String query = intent.getStringExtra(SearchManager.QUERY);
            doMySearch(query);
        }
    }
    private void doMySearch(String query){
        List<ItemObject> dictionaryObject = databaseObject.searchDictionaryWords(query);
        mSearchAdapter = new SearchAdapter(SearchableActivity.this, dictionaryObject);
        listView.setAdapter(mSearchAdapter);
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.search_menu, menu);
        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        searchView.setIconifiedByDefault(false);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.search:
                onSearchRequested();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

We have created a custom SearchAdapter class which inherits from BaseAdapter class. The complete code for the adapter class is as shown below.

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.inducesmile.androidsearchdialog.database.ItemObject;
import java.util.List;
public class SearchAdapter extends BaseAdapter {
    private LayoutInflater layoutInflater;
    private List<ItemObject> listItemStorage;
    public SearchAdapter(Context context, List<ItemObject> customizedListView) {
        layoutInflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        listItemStorage = customizedListView;
    }
    @Override
    public int getCount() {
        return listItemStorage.size();
    }
    @Override
    public Object getItem(int position) {
        return listItemStorage.get(position);
    }
    @Override
    public long getItemId(int position) {
        return position;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder listViewHolder;
        if(convertView == null){
            listViewHolder = new ViewHolder();
            convertView = layoutInflater.inflate(R.layout.item_list, parent, false);
            listViewHolder.dictionaryWord = (TextView)convertView.findViewById(R.id.list_item_search);
            convertView.setTag(listViewHolder);
        }else{
            listViewHolder = (ViewHolder)convertView.getTag();
        }
        listViewHolder.dictionaryWord.setText(listItemStorage.get(position).getWord());
        return convertView;
    }
    static class ViewHolder{
        TextView dictionaryWord;
    }
}

You can see that we used a List to hold the Entity object that wraps our individual item data in ItemObject class. This is the code for this class.

public class ItemObject {
    private int id;
    private String word;
    private String meaning;
    public ItemObject(int id, String word) {
        this.id = id;
        this.word = word;
    }
    public ItemObject(int id, String word, String meaning) {
        this.id = id;
        this.word = word;
        this.meaning = meaning;
    }
    public int getId() {
        return id;
    }
    public String getWord() {
        return word;
    }
    public String getMeaning() {
        return meaning;
    }
}

You can also see that our custom adapter class inflate a layout file – list_item.xml. The contain of this layout file is as shown.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/list_item_search"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/colorBlack"
        android:padding="16dp"
        android:text="@string/app_name"/>
</LinearLayout>

We stated that our datasource is stored in a local SQLite database. I will not go into details about the database class but the code for classes involved with database connection and retrieval of data is shown below.

DatabaseConfig.java

import android.content.Context;
import com.readystatesoftware.sqliteasset.SQLiteAssetHelper;
public class DatabaseConfig extends SQLiteAssetHelper {
    private static final String DATABASE_NAMES = "dictionary";
    private static final int DATABASE_VERSION = 1;
    public DatabaseConfig(Context context) {
        super(context, DATABASE_NAMES, null, DATABASE_VERSION);
    }
}

DatabaseInstance – DbObject.java

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
public class DbObject {
    private static DatabaseConfig dbHelper;
    private SQLiteDatabase db;
    public DbObject(Context context) {
        dbHelper = new DatabaseConfig(context);
        this.db = dbHelper.getReadableDatabase();
    }
    public SQLiteDatabase getDbConnection(){
        return this.db;
    }
    public void closeDbConnection(){
        if(this.db != null){
            this.db.close();
        }
    }
}

DbBackend.java

import android.content.Context;
import android.database.Cursor;
import java.util.ArrayList;
import java.util.List;
public class DbBackend extends DbObject{
    public DbBackend(Context context) {
        super(context);
    }
    public List<ItemObject> searchDictionaryWords(String searchWord){
        List<ItemObject> mItems = new ArrayList<ItemObject>();
        String query = "Select * from dictionary where word like " + "'%" + searchWord + "%'";
        Cursor cursor = this.getDbConnection().rawQuery(query, null);
        ArrayList<String> wordTerms = new ArrayList<String>();
        if(cursor.moveToFirst()){
            do{
                int id = cursor.getInt(0);
                String word = cursor.getString(cursor.getColumnIndexOrThrow("word"));
                mItems.add(new ItemObject(id, word));
            }while(cursor.moveToNext());
        }
        cursor.close();
        return mItems;
    }
}

Since I made use of SQLiteAssetHelper third party library for the database handling, we will add this single line of code to our app build.gradle dependencies section as shown below.

compile 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'

This brings us to the end of the first tutorial.

Using SearchView as an Activity UI

Although there are many similarity between the first android example project with the second but we will only focus on their differences. You can reuse some of the codes above to complete this tutorial.

So create a new android project. Following the process describe before on how to create a new android project in Android Studio.

Name your new android project – AndroidSearchView.

You can leave the default activity name or change it to whatever you like.

After you have created the new project, copy almost all the files we created in the first tutorial here or you can downloaded the complete source code of this project to see what is included and what has been left out.

First, we will look into the activity_main.xml file which is the main layout file for our project. We will combine a ListView and SearchView widgets in the layout. The code sample is as shown below.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp"
    android:orientation="vertical">
    <android.support.v7.widget.SearchView
        android:id="@+id/searchView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:elevation="4dp"
        android:background="@color/colorWhite">
    </android.support.v7.widget.SearchView>
    <ListView
        android:id="@+id/listView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="16dp"
        android:layout_marginTop="16dp"
        android:padding="8dp"
        android:scrollbars="none" />
</LinearLayout>

For the MainActivity class, we are following the same process like we did in the first project but for the SearchView widget, we will obtain an instance of the SearchView and we will attach setOnQueryTextListener method which will monitor when there is text change in the SearchView widget.

An object of SearchView.OnQueryTextListener() which implements onQueryTextSubmit(String query) and onQueryTextChange(String newText) method. The event implementation is as shown.

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    @Override
    public boolean onQueryTextSubmit(String query) {
        List<ItemObject> dictionaryObject = databaseObject.searchDictionaryWords(query);
        SearchAdapter mSearchAdapter = new SearchAdapter(MainActivity.this, dictionaryObject);
        listView.setAdapter(mSearchAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            }
        });
        return true;
    }
    @Override
    public boolean onQueryTextChange(String newText) {
        return false;
    }
});

The complete code for the MainActivity class is shown below.

package com.inducesmile.androidsearchview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SearchView;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import com.inducesmile.androidsearchview.database.DbBackend;
import com.inducesmile.androidsearchview.database.ItemObject;
import java.util.List;
public class MainActivity extends AppCompatActivity {
    SearchView searchView;
    private ListView listView;
    private DbBackend databaseObject;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        searchView = (SearchView) findViewById(R.id.searchView);
        searchView.setQueryHint("Enter search");
        databaseObject = new DbBackend(MainActivity.this);
        listView = (ListView)findViewById(R.id.listView);
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                List<ItemObject> dictionaryObject = databaseObject.searchDictionaryWords(query);
                SearchAdapter mSearchAdapter = new SearchAdapter(MainActivity.this, dictionaryObject);
                listView.setAdapter(mSearchAdapter);
                listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                    @Override
                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    }
                });
                return true;
            }
            @Override
            public boolean onQueryTextChange(String newText) {
                return false;
            }
        });
    }
}

When you run your application you should see an interface like the one below.

searchview

Finally, this brings us to the end of this tutorial.

You can download the code for this tutorial below. If you are having hard time downloading the tutorials, kindly contact me.

Remember to subscribe with your email address so that you will be among the first to receive my new post once it is published.

Please if you love this tutorial, kindly download my new android app – Daily Calculator Tool – in Google Play Store and let me know what you think about it.

OTHER INTERESTING POSTS:

3 Comments

Add a Comment