Filter the data loaded from Server(PHP MySQL or JSON file) in respond to user input and show them on autocomplete view as search suggestions.

This tutorial only covers, how to implement autocomplete search suggestions for search bar/searchview in android and if you want to display result with recyclerview after user enters query and go, then please refer tutorial Android Search View with PHP MySQL.

Data from PHP MySQL or JSON file is loaded using AsyncTask and if you are using any library like retrofit or volley to fetch data from server then you may remove AsyncTask and replace appropriate code.

DOWNLOAD CODE FROM GITHUB

Search Suggestions Video Demo

Some approaches to Search Suggestions when loading data from server

Here are some approaches that i think of which may help you based on your circumstances.

  1. Load data from server everytime when user enters a character into search bar(Make call to PHP file and apply query with LIKE statement in MySQL).
  2. Load all data from server before launching search activity, filter data based on user input and display search suggestions.
  3. Load data from server before launching search activity and store those data into SQLite database and when fetching next time fetch only new records and update your SQLite database.

The first approach if you consider, the response time and delay is high and user may or may not get search suggestions for entered query.

Here, i considered second approach for autocomplete search suggestions as my data is some few records.

Implementing Autocomplete Search Suggestions

MySQL

The structure of table ‘tbl_fish’ defined as below.

CREATE TABLE IF NOT EXISTS `tbl_fish` (
  `fish_id` int(11) NOT NULL AUTO_INCREMENT,
  `fish_name` varchar(255) NOT NULL
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

Table ‘tbl_fish’ contains data like this.

INSERT INTO `tbl_fish` (`fish_id`, `fish_name`) VALUES
(1, 'Indian Mackerel'),
(2, 'Manthal Repti'),
(3, 'Baby Sole Fish'),
(4, 'Clam Meat'),
(5, 'Indian Prawn'),
(6, 'Lamp'),
(7, 'Kandike');

PHP and JSON

config.inc.php

The configuration file for PHP.

<?php

$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "test";

try {
    	$connection = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
    	$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }
catch(PDOException $e)
    {
    	die("OOPs something went wrong");
    }

?>

fetch-all-fish.php

This file is responsible for fetching data from database return data back to android in JSON format.

<?php
		
  require_once('config.inc.php');
  $sql = 'SELECT * from tbl_fish';
  $statement = $connection->prepare($sql);
  $statement->execute();
  if($statement->rowCount())
  {
    $row_all = $statement->fetchall(PDO::FETCH_ASSOC);
    header('Content-type: application/json');
    echo json_encode($row_all); 		
  }  
  elseif(!$statement->rowCount())
  {
    echo "no rows";
  }
		  
?>

The above PHP file output’s data in JSON format however if you don’t want to use PHP MySQL with search suggestions then entering path of your JSON file say http://192.168.1.7/test/fetch-all-fish.JSON in your MainActivity.java also works. Below is the example JSON file.

fetch-all-fish.JSON

Create this file if you don’t want to use PHP MySQL.

[
 {"fish_id":"1","fish_name":"Indian Mackerel"},
 {"fish_id":"2","fish_name":"Manthal Repti"},
 {"fish_id":"3","fish_name":"Baby Sole Fish"},
 {"fish_id":"4","fish_name":"Clam Meat"},
 {"fish_id":"5","fish_name":"Indian Prawn"},
 {"fish_id":"6","fish_name":"Lamp"},
 {"fish_id":"7","fish_name":"Kandike"}
]

Android

Files which are directly involved in android autocomplete search suggestions are.

MainActivity.java

MainActivity.java is responsible for following activities.

  • Define SearchView/Search bar on actionbar/toolbar
  • We defined AsyncTask class to fetch data from PHP or JSON file.
  • SimpleCursorAdapter is used to bind fetched data from server.
  • Define OnSuggestionListener and OnQueryTextListener for searchview to handle events.
  • Use changeCursor method of SimpleCursorAdapter to filter data.

File Location: App→java→Your package

package com.androidcss.searchexample;

import android.app.ProgressDialog;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.database.MatrixCursor;
import android.os.AsyncTask;
import android.provider.BaseColumns;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import android.database.Cursor;

public class MainActivity extends AppCompatActivity {

    // CONNECTION_TIMEOUT and READ_TIMEOUT are in milliseconds
    public static final int CONNECTION_TIMEOUT = 10000;
    public static final int READ_TIMEOUT = 15000;
    private SimpleCursorAdapter myAdapter;

    SearchView searchView = null;
    private String[] strArrData = {"No Suggestions"};


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

        final String[] from = new String[] {"fishName"};
        final int[] to = new int[] {android.R.id.text1};

        // setup SimpleCursorAdapter
        myAdapter = new SimpleCursorAdapter(MainActivity.this, android.R.layout.simple_spinner_dropdown_item, null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

        // Fetch data from mysql table using AsyncTask
        new AsyncFetch().execute();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        // adds item to action bar
        getMenuInflater().inflate(R.menu.search_main, menu);

        // Get Search item from action bar and Get Search service
        MenuItem searchItem = menu.findItem(R.id.action_search);
        SearchManager searchManager = (SearchManager) MainActivity.this.getSystemService(Context.SEARCH_SERVICE);
        if (searchItem != null) {
            searchView = (SearchView) searchItem.getActionView();
        }
        if (searchView != null) {
            searchView.setSearchableInfo(searchManager.getSearchableInfo(MainActivity.this.getComponentName()));
            searchView.setIconified(false);
            searchView.setSuggestionsAdapter(myAdapter);
            // Getting selected (clicked) item suggestion
            searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
                @Override
                public boolean onSuggestionClick(int position) {

                    // Add clicked text to search box
                    CursorAdapter ca = searchView.getSuggestionsAdapter();
                    Cursor cursor = ca.getCursor();
                    cursor.moveToPosition(position);
                    searchView.setQuery(cursor.getString(cursor.getColumnIndex("fishName")),false);
                    return true;
                }

                @Override
                public boolean onSuggestionSelect(int position) {
                    return true;
                }
            });
            searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String s) {
                    return false;
                }

                @Override
                public boolean onQueryTextChange(String s) {

                    // Filter data
                    final MatrixCursor mc = new MatrixCursor(new String[]{ BaseColumns._ID, "fishName" });
                    for (int i=0; i<strArrData.length; i++) {
                        if (strArrData[i].toLowerCase().startsWith(s.toLowerCase()))
                            mc.addRow(new Object[] {i, strArrData[i]});
                    }
                    myAdapter.changeCursor(mc);
                    return false;
                }
            });
        }

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        return super.onOptionsItemSelected(item);
    }


    // Every time when you press search button on keypad an Activity is recreated which in turn calls this function
    @Override
    protected void onNewIntent(Intent intent) {

        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
            String query = intent.getStringExtra(SearchManager.QUERY);
            if (searchView != null) {
                searchView.clearFocus();
            }

            // User entered text and pressed search button. Perform task ex: fetching data from database and display

        }
    }

    // Create class AsyncFetch
    private class AsyncFetch extends AsyncTask<String, String, String> {

        ProgressDialog pdLoading = new ProgressDialog(MainActivity.this);
        HttpURLConnection conn;
        URL url = null;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            //this method will be running on UI thread
            pdLoading.setMessage("\tLoading...");
            pdLoading.setCancelable(false);
            pdLoading.show();

        }

        @Override
        protected String doInBackground(String... params) {
            try {

                // Enter URL address where your php file resides or your JSON file address
                url = new URL("http://192.168.1.7/test/fetch-all-fish1.php");

            } catch (MalformedURLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                return e.toString();
            }
            try {

                // Setup HttpURLConnection class to send and receive data from php and mysql
                conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(READ_TIMEOUT);
                conn.setConnectTimeout(CONNECTION_TIMEOUT);
                conn.setRequestMethod("GET");

                // setDoOutput to true as we receive data
                conn.setDoOutput(true);
                conn.connect();

            } catch (IOException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
                return e1.toString();
            }

            try {

                int response_code = conn.getResponseCode();

                // Check if successful connection made
                if (response_code == HttpURLConnection.HTTP_OK) {

                    // Read data sent from server
                    InputStream input = conn.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(input));
                    StringBuilder result = new StringBuilder();
                    String line;

                    while ((line = reader.readLine()) != null) {
                        result.append(line);
                    }

                    // Pass data to onPostExecute method
                    return (result.toString());

                } else {
                    return("Connection error");
                }

            } catch (IOException e) {
                e.printStackTrace();
                return e.toString();
            } finally {
                conn.disconnect();
            }


        }

        @Override
        protected void onPostExecute(String result) {

            //this method will be running on UI thread
            ArrayList<String> dataList = new ArrayList<String>();
            pdLoading.dismiss();


            if(result.equals("no rows")) {

                // Do some action if no data from database

            }else{

                try {

                    JSONArray jArray = new JSONArray(result);

                    // Extract data from json and store into ArrayList
                    for (int i = 0; i < jArray.length(); i++) {
                        JSONObject json_data = jArray.getJSONObject(i);
                        dataList.add(json_data.getString("fish_name"));
                    }

                    strArrData = dataList.toArray(new String[dataList.size()]);

                } catch (JSONException e) {
                    // You to understand what actually error is and handle it appropriately
                    Toast.makeText(MainActivity.this, e.toString(), Toast.LENGTH_LONG).show();
                    Toast.makeText(MainActivity.this, result.toString(), Toast.LENGTH_LONG).show();
                }

            }

        }

    }
}

search_main.xml

location: res→menu resource directory.

If you don’t find menu directory inside res folder, then add one by Right clicking on res directory(Right click on res→New→Android Resource Directory).

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" tools:context=".SearchResultsActivity">

    <item
        android:id="@+id/action_search"
        android:icon="@android:drawable/ic_menu_search"
        app:showAsAction="always"
        app:actionViewClass="android.support.v7.widget.SearchView"
        android:title="Search"/>

</menu>

searchable.xml

This file should be placed inside res→xml resource directory. If you don’t find any xml folder inside res directory, then add one by Right clicking on res directory(Right click on res→New→Android Resource Directory).

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

styles.xml

In my theme, by default the color of AutoComplete box and text is in black and white color respectively so i changed it to light grey and black color by defining my own AutoCompleteTextView style as you can see it in below image. The code responsible for AutoCompleteTextView is highlighted below.

Android AutoCompleteTextView Styling

Android AutoCompleteTextView Styling

File Location: App→res→Values

<resources>

    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="autoCompleteTextViewStyle">@style/myAutoCompleteTextViewStyle</item>
        <item name="textAppearanceSearchResultTitle">@style/mySearchResult</item>
    </style>

    <style name="myAutoCompleteTextViewStyle" parent="Widget.AppCompat.Light.AutoCompleteTextView">
        <item name="android:popupBackground">#EEE</item>
        <item name="android:colorFocusedHighlight">#000000</item>
    </style>

    <style name="mySearchResult" parent="TextAppearance.AppCompat.SearchResult.Title">
        <item name="android:textColor">#FFF</item>
    </style>

</resources>

AndroidManifest.xml

Don’t forget to add below highlighted code to your AndroidManifest.xml file.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.androidcss.searchexample" >
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <meta-data
            android:name="android.app.default_searchable"
            android:value=".MainActivity" />

        <activity android:name=".MainActivity" android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <action android:name="android.intent.action.SEARCH" />
                <action android:name="android.intent.action.VIEW" />
            </intent-filter>
            <meta-data
                android:name="android.app.searchable"
                android:resource="@xml/searchable" />
        </activity>
    </application>

</manifest>