AndroidCSS

Android material design Programming Blog

Android AutoComplete Search Suggestions from PHP MySQL Server

Filter the data loaded from Server(PHP MySQL or JSON file) in responding 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 the result with RecyclerView after the user enters a query and go, then please refer tutorial Android Search View with PHP MySQL.

The AsyncTask will make a request to PHP MySQL server and get the response data in JSON format. To fetch data from the server if you want to use any library like retrofit or volley, then you have to remove AsyncTask and replace appropriate code.

DOWNLOAD CODE FROM GITHUB

Search Suggestions Video Demo

Some approaches to Search Suggestions when loading the data from a server

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

  1. Load data from server everytime when the user enters a character into the search bar(Make a call to PHP file and apply query with LIKE statement in MySQL).
  2. Retrieve all data from the server before launching search activity, filter data based on user input and display search suggestions.
  3. Fetch data from the 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 are high and a user may or may not get search suggestions for entered query.

Here, I considered the 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 action bar/toolbar
  • We defined AsyncTask class to fetch data from PHP or JSON file.
  • To bind fetched data from the server, we useSimpleCursorAdapter
  • 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 has to go under 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>

21 Comments

Add yours

  1. hi, thanks for your tutorial, but i get this error
    android.support.v7.widget.SearchView cannot be cast to android.widget.SearchView
    need help
    thanks

  2. What is BaseColumns._ID? from this I think I’m not getting another value while I’m sending data to another activity.

  3. fetching data in search bar its all going well no doubt but i want to send data to next activity for eg in name search xyz and xyz have phone details email id etc.. but i m sending only one data that’s it name i m not getting the number mail id etc its show
    java.lang.IllegalArgumentException: columnNames.length = 3, columnValues.length = 2
    MatrixCursor.addRow

    it’s a request to solve my problem give a reply

    thanks

    • Rather than passing string variable, you can pass object to next activity. If you dont understand, please post your code to analyze further.

  4. where is activity_main.xml file?

  5. searchView.setSuggestionsAdapter(myAdapter);
    Error in myAdapter.

  6. Thank you. This tutorial is helpful for me to do my project

  7. nice work and clean codes
    what if i have this type of json data
    [
    {"fish_id":"1","fish_name":"Indian Mackerel","fish_weight":"2kg","price":"$10"},
    {"fish_id":"2","fish_name":"Manthal Repti","fish_weight":"0.5kg","price":"$3"},
    {"fish_id":"3","fish_name":"Baby Sole Fish","fish_weight":"5kg","price":"$12"},
    {"fish_id":"4","fish_name":"Clam Meat","fish_weight":"1kg","price":"$7"},
    {"fish_id":"5","fish_name":"Indian Prawn","fish_weight":"0.2kg","price":"$5"}
    ]

    when auto suggest item clicked i want to display other related data i mean price and fish_weight how can i achieve that?

  8. This is implemented without any bugs, Once the autocomplete search fills up how to load the data from the server regarding that search field value?

  9. Nice tutorial.
    But I am facing a problem when retrieving the data from MySQL. The app can fetch data without any problem if I use the mobile data to connect to the internet. But if I use Wifi to connect to the internet, the data cannot be fetched and it shows error “Connection error” and an exception.

  10. Hi, I’m using BaseAdapter how do I filter the result using ArrayAdapter?
    Currently, all the data are populated in the ListView.

    i’m using getFilter() but it could not work as I posted in http://stackoverflow.com/questions/40147767/getfilter-in-adapter-not-working-android

    Hope you can give some advice.
    Thanks

  11. thank you for this advanced programming
    i have a quetion
    how i parse or display JSON when i press search button ??

  12. Programming is combination of intelligent and creative work. Programmers can do anything with code. The entire Programming tutorials that you mention here on this blog are awesome. also provides latest tutorials of Programming from beginning to advance level.
    Be with us to learn programming in new and creative way.

Leave a Reply

Your email address will not be published.

*

About | Policy | Disclaimer

Creative Commons LicenceUp ↑