Quantcast
Channel: Knowledge by Experience » Android
Viewing all articles
Browse latest Browse all 23

Adding custom search suggestions to search dialog from SQLite database in Android

$
0
0

In this article, we will develop an Android application containing a search dialog, that can search a set of countries stored in an SQLite database. The search dialog is facilitated with custom search suggestions containing texts and images. The search dialog fetches the data from SQLite database using cursor loader via content provider.



Major files used in this application are listed below :

  • MainActivity.java : Search dialog is invoked from the MainActivity class defined in this file.
  • SearchableActivity.java : Actions performed on search dialog is handled by SearchableActivity.
  • CountryActivity.java : The details of the selected country is displayed in this activity. The selection can be done either from suggestion for search dialog or from listview of SearchableActivity.
  • searchable.xml : This is the configuration file for search dialog which defines various properties like content provider for custom suggestions, intent data for selected suggested item, search suggestions to quick search box etc
  • CountryContentProvider.java : The content provider that provides necessary data for the search dialog from table class.
  • CountryDB.java : A table class that fetches data from the SQLite database.
This application is developed in Eclipse (4.2.1) with ADT plugin (22.0.1) and Android SDK (22.0.1).

1. Create new Android application project namely “SearchDialogDemo” with the given below details

Application Name : SearchDialogDemo

Project Name : SearchDialogDemo

Package Name : in.wptrafficanalyzer.searchdialogdemo

Minimum Required SDK : API 8 : Android 2.2 ( Froyo )

Target SDK : API 17 : Android 4.2 ( Jelly Bean )


2. Add Android Support library to this project

By default, Android support library (android-support-v4.jar ) is added to this project by Eclipse IDE to the directory libs. If it is not added, we can do it manually by doing the following steps :

  • Open Project Explorer by Clicking “Window -> Show View -> Project Explorer”
  • Right click this project
  • Then from popup menu, Click “Android Tools -> Add Support Library “

3. Create a folder namely res/drawable and extract the zip file to into it.


4. Update the file res/values/strings.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">SearchDialogDemo</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    <string name="search">Search Country</string>
    <string name="search_hint">Search names</string>
    <string name="flag_description">Country Flag</string>
    <string name="search_settings">Search Country Names</string>

</resources>

5. Create the layout file res/layout/activity_main.xml

<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=".MainActivity" >

    <Button
        android:id="@+id/btn_search"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/search"
        android:layout_centerInParent="true" />

</RelativeLayout>

6. Create the layout file res/layout/activity_searchable.xml


<?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:orientation="vertical" >

    <ListView
        android:id="@+id/lv_countries"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

    </LinearLayout>

7. Create the layout file res/layout/activity_country.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ImageView
        android:id="@+id/iv_flag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:contentDescription="@string/flag_description"/>

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/iv_flag"
        android:textSize="25sp" />

    <TextView
        android:id="@+id/tv_currency"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_name"
        android:textSize="25sp" />

</RelativeLayout>

8. Create a class Country in the file src/in/wptrafficanalyzer/searchdialogdemo/Country.java

package in.wptrafficanalyzer.searchdialogdemo;

/** Country details are stored in this class and is used to populate the table countries
 * in CountryDb.java
 */
public class Country {
    // Array of strings storing country names
    static String[] countries = new String[] {
        "India",
        "Pakistan",
        "Sri Lanka",
        "China",
        "Bangladesh",
        "Nepal",
        "Afghanistan",
        "North Korea",
        "South Korea",
        "Japan"
    };

    // Array of integers points to images stored in /res/drawable
        static int[] flags = new int[]{
        R.drawable.india,
        R.drawable.pakistan,
        R.drawable.srilanka,
        R.drawable.china,
        R.drawable.bangladesh,
        R.drawable.nepal,
        R.drawable.afghanistan,
        R.drawable.nkorea,
        R.drawable.skorea,
        R.drawable.japan
    };

    // Array of strings to store currencies
    static String[] currency = new String[]{
        "Indian Rupee",
        "Pakistani Rupee",
        "Sri Lankan Rupee",
        "Renminbi",
        "Bangladeshi Taka",
        "Nepalese Rupee",
        "Afghani",
        "North Korean Won",
        "South Korean Won",
        "Japanese Yen"
    };
}

9. Create the class CountryDB in the file src/in/wptrafficanalyzer/searchdialogdemo/CountryDB.java


package in.wptrafficanalyzer.searchdialogdemo;

import java.util.HashMap;

import android.app.SearchManager;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;

public class CountryDB{

    private static final String DBNAME = "country";
    private static final int VERSION = 1;
    private CountryDBOpenHelper mCountryDBOpenHelper;
    private static final String FIELD_ID = "_id";
    private static final String FIELD_NAME = "name";
    private static final String FIELD_FLAG = "flag";
    private static final String FIELD_CURRENCY = "currency";
    private static final String TABLE_NAME = "countries";
    private HashMap<String, String> mAliasMap;

    public CountryDB(Context context){
        mCountryDBOpenHelper = new CountryDBOpenHelper(context, DBNAME, null, VERSION);

        // This HashMap is used to map table fields to Custom Suggestion fields
        mAliasMap = new HashMap<String, String>();

        // Unique id for the each Suggestions ( Mandatory )
        mAliasMap.put("_ID", FIELD_ID + " as " + "_id" );

        // Text for Suggestions ( Mandatory )
        mAliasMap.put(SearchManager.SUGGEST_COLUMN_TEXT_1, FIELD_NAME + " as " + SearchManager.SUGGEST_COLUMN_TEXT_1);

// Icon for Suggestions ( Optional )
        mAliasMap.put( SearchManager.SUGGEST_COLUMN_ICON_1, FIELD_FLAG + " as " + SearchManager.SUGGEST_COLUMN_ICON_1);

        // This value will be appended to the Intent data on selecting an item from Search result or Suggestions ( Optional )
        mAliasMap.put( SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, FIELD_ID + " as " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID );
    }

    /** Returns Countries */
    public Cursor getCountries(String[] selectionArgs){

        String selection = FIELD_NAME + " like ? ";

        if(selectionArgs!=null){
            selectionArgs[0] = "%"+selectionArgs[0] + "%";
        }

        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        queryBuilder.setProjectionMap(mAliasMap);

        queryBuilder.setTables(TABLE_NAME);

        Cursor c = queryBuilder.query(mCountryDBOpenHelper.getReadableDatabase(),
            new String[] { "_ID",
                            SearchManager.SUGGEST_COLUMN_TEXT_1 ,
                            SearchManager.SUGGEST_COLUMN_ICON_1 ,
                            SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID } ,
            selection,
            selectionArgs,
            null,
            null,
            FIELD_NAME + " asc ","10"
        );
        return c;
    }

    /** Return Country corresponding to the id */
    public Cursor getCountry(String id){

        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

        queryBuilder.setTables(TABLE_NAME);

        Cursor c = queryBuilder.query(mCountryDBOpenHelper.getReadableDatabase(),
            new String[] { "_id", "name", "flag", "currency" } ,
            "_id = ?", new String[] { id } , null, null, null ,"1"
        );

        return c;
    }
    class CountryDBOpenHelper extends SQLiteOpenHelper{

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

        @Override
        public void onCreate(SQLiteDatabase db) {
            String sql = "";

            // Defining table structure
            sql =   " create table " + TABLE_NAME + "" +
                    " ( " +
                    FIELD_ID + " integer primary key autoincrement, " +
                    FIELD_NAME + " varchar(100), " +
                    FIELD_FLAG + " int, " +
                    FIELD_CURRENCY + " varchar(100) " +
                    " ) " ;

            // Creating table
            db.execSQL(sql);

            for(int i=0;i<Country.countries.length;i++){

                // Defining insert statement
                sql = "insert into " + TABLE_NAME + " ( " +
                        FIELD_NAME + " , " +
                        FIELD_FLAG + " , " +
                        FIELD_CURRENCY + " ) " +
                        " values ( " +
                            " '" + Country.countries[i] + "' ," +
                            " " + Country.flags[i] + " ," +
                            " '" + Country.currency[i] + "' ) ";

                // Inserting values into table
                db.execSQL(sql);
            }
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            // TODO Auto-generated method stub
        }
    }
}

10. Creating the content provider in the file “src/in/wptrafficanalyzer/searchdialogdemo/CountryContentProvider.java


package in.wptrafficanalyzer.searchdialogdemo;

import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;

public class CountryContentProvider extends ContentProvider {

    public static final String AUTHORITY = "in.wptrafficanalyzer.searchdialogdemo.CountryContentProvider";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/countries" );

    CountryDB mCountryDB = null;

    private static final int SUGGESTIONS_COUNTRY = 1;
    private static final int SEARCH_COUNTRY = 2;
    private static final int GET_COUNTRY = 3;

    UriMatcher mUriMatcher = buildUriMatcher();

    private UriMatcher buildUriMatcher(){
        UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        // Suggestion items of Search Dialog is provided by this uri
        uriMatcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY,SUGGESTIONS_COUNTRY);

        // This URI is invoked, when user presses "Go" in the Keyboard of Search Dialog
        // Listview items of SearchableActivity is provided by this uri
        // See android:searchSuggestIntentData="content://in.wptrafficanalyzer.searchdialogdemo.provider/countries" of searchable.xml
        uriMatcher.addURI(AUTHORITY, "countries", SEARCH_COUNTRY);

        // This URI is invoked, when user selects a suggestion from search dialog or an item from the listview
        // Country details for CountryActivity is provided by this uri
        // See, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID in CountryDB.java
        uriMatcher.addURI(AUTHORITY, "countries/#", GET_COUNTRY);

        return uriMatcher;
    }

    @Override
    public boolean onCreate() {
        mCountryDB = new CountryDB(getContext());
        return true;
    }

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

        Cursor c = null;
        switch(mUriMatcher.match(uri)){
            case SUGGESTIONS_COUNTRY :
                c = mCountryDB.getCountries(selectionArgs);
                break;
            case SEARCH_COUNTRY :
                c = mCountryDB.getCountries(selectionArgs);
                break;
            case GET_COUNTRY :
                String id = uri.getLastPathSegment();
                c = mCountryDB.getCountry(id);
        }

        return c;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
        String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }
}

11. Create a class namely MainActivity in the file src/in/wptrafficanalyzer/searchdialogdemo/MainActivity.java

package in.wptrafficanalyzer.searchdialogdemo;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends FragmentActivity{

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

        Button btn = (Button) findViewById(R.id.btn_search);

        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                onSearchRequested();
            }
        });
    }
}

12. Create the class namely “SearchableActivity” in the file src/in/wptrafficanalyzer/searchdialogdemo/SearchableActivity.java


package in.wptrafficanalyzer.searchdialogdemo;

import android.app.SearchManager;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.SearchRecentSuggestions;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;

public class SearchableActivity extends FragmentActivity implements LoaderCallbacks<Cursor> {

    ListView mLVCountries;
    SimpleCursorAdapter mCursorAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_searchable);

        // Getting reference to Country List
        mLVCountries = (ListView)findViewById(R.id.lv_countries);

        // Setting item click listener
        mLVCountries.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                Intent countryIntent = new Intent(getApplicationContext(), CountryActivity.class);

                // Creating a uri to fetch country details corresponding to selected listview item
                Uri data = Uri.withAppendedPath(CountryContentProvider.CONTENT_URI, String.valueOf(id));

                // Setting uri to the data on the intent
                countryIntent.setData(data);

                // Open the activity
                startActivity(countryIntent);
            }
        });

        // Defining CursorAdapter for the ListView
        mCursorAdapter = new SimpleCursorAdapter(getBaseContext(),
            android.R.layout.simple_list_item_1,
            null,
            new String[] { SearchManager.SUGGEST_COLUMN_TEXT_1},
            new int[] { android.R.id.text1}, 0);

        // Setting the cursor adapter for the country listview
        mLVCountries.setAdapter(mCursorAdapter);

        // Getting the intent that invoked this activity
        Intent intent = getIntent();

        // If this activity is invoked by selecting an item from Suggestion of Search dialog or
        // from listview of SearchActivity
        if(intent.getAction().equals(Intent.ACTION_VIEW)){
            Intent countryIntent = new Intent(this, CountryActivity.class);
            countryIntent.setData(intent.getData());
            startActivity(countryIntent);
            finish();
        }else if(intent.getAction().equals(Intent.ACTION_SEARCH)){ // If this activity is invoked, when user presses "Go" in the Keyboard of Search Dialog
            String query = intent.getStringExtra(SearchManager.QUERY);
            doSearch(query);
        }
    }

    private void doSearch(String query){
        Bundle data = new Bundle();
        data.putString("query", query);

        // Invoking onCreateLoader() in non-ui thread
        getSupportLoaderManager().initLoader(1, data, this);
    }
    /** This method is invoked by initLoader() */
    @Override
    public Loader<Cursor> onCreateLoader(int arg0, Bundle data) {
        Uri uri = CountryContentProvider.CONTENT_URI;
        return new CursorLoader(getBaseContext(), uri, null, null , new String[]{data.getString("query")}, null);
    }

    /** This method is executed in ui thread, after onCreateLoader() */
    @Override
        public void onLoadFinished(Loader<Cursor> arg0, Cursor c) {
        mCursorAdapter.swapCursor(c);
    }
    @Override
    public void onLoaderReset(Loader<Cursor> arg0) {
        // TODO Auto-generated method stub
    }
}

13. Create the class “CountryActivity” in the file src/in/wptrafficanalyzer/searchdialogdemo/CountryActivity.java

package in.wptrafficanalyzer.searchdialogdemo;

import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.widget.ImageView;
import android.widget.TextView;

public class CountryActivity extends FragmentActivity implements LoaderCallbacks<Cursor>{

    private Uri mUri;
    private ImageView mIvFlag;
    private TextView mTvName;
    private TextView mTvCurrency;

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        setContentView(R.layout.activity_country);

        Intent intent = getIntent();
        mUri = intent.getData();

        mIvFlag = (ImageView) findViewById(R.id.iv_flag);
        mTvName = (TextView) findViewById(R.id.tv_name);
        mTvCurrency = (TextView) findViewById(R.id.tv_currency);

        // Invokes the method onCreateloader() in non-ui thread
        getSupportLoaderManager().initLoader(0, null, this);

    }

    /** Invoked by initLoader() */
    @Override
    public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
        return new CursorLoader(getBaseContext(), mUri, null, null , null, null);
    }

    /** Invoked by onCreateLoader(), will be executed in ui-thread */
    @Override
    public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
        if(cursor.moveToFirst()){
            mIvFlag.setImageResource(cursor.getInt(cursor.getColumnIndex(cursor.getColumnName(2))));
            mTvName.setText("Country: "+cursor.getString(cursor.getColumnIndex(cursor.getColumnName(1))));
            mTvCurrency.setText("Currency: "+cursor.getString(cursor.getColumnIndex(cursor.getColumnName(3))));
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> arg0) {
        // TODO Auto-generated method stub
    }
}

14. Create the configuration file for search dialog in the file res/xml/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_hint"
    android:searchSettingsDescription="@string/search_settings"

    android:searchSuggestAuthority="in.wptrafficanalyzer.searchdialogdemo.CountryContentProvider"
    android:searchSuggestIntentAction="android.intent.action.VIEW"
    android:searchSuggestIntentData="content://in.wptrafficanalyzer.searchdialogdemo.CountryContentProvider/countries"
    android:searchSuggestSelection=" ?"
    android:searchSuggestThreshold="1"

    android:includeInGlobalSearch="true" >
</searchable>

15. Update the file AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="in.wptrafficanalyzer.searchdialogdemo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

    <!-- Activity with SearchDialog enabled -->
    <activity
        android:name="in.wptrafficanalyzer.searchdialogdemo.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    <!-- Enabling Search Dialog -->
    <meta-data android:name="android.app.default_searchable"
        android:value=".SearchableActivity" />
    </activity>

    <!-- A Searchable activity, that handles the searches -->
    <activity android:name=".SearchableActivity" >
        <intent-filter>
            <action android:name="android.intent.action.SEARCH" />
        </intent-filter>
        <meta-data android:name="android.app.searchable"
            android:resource="@xml/searchable"/>
    </activity>

    <!-- Activity that shows the country details -->
    <activity android:name=".CountryActivity" />

    <!-- Content Provider to query sqlite database -->
    <provider
        android:name=".CountryContentProvider"
        android:authorities="in.wptrafficanalyzer.searchdialogdemo.CountryContentProvider"
        android:exported="true" />
    </application>

</manifest>

16. Screenshots of the application

MainActivity of the application

Figure 1 : MainActivity of the application

Showing Custom Suggestions from SQLite database

Figure 2 : Showing Custom Suggestions from SQLite database

Showing country details in CountryActivity

Figure 3 : Showing country details in CountryActivity

Searching in Quick  Search Box

Figure 4 : Searching in Quick Search Box


17. Download Source Code



Viewing all articles
Browse latest Browse all 23

Trending Articles