ZOFTINO.COM android and web dev tutorials

AutoCompleteTextView Dynamic Serach from Database

Using AutoCompleteTextView, you can show help text as drop down while user typing text into the text field. If for a particular field, there are only few possible values in your application, you can load the data from source and add it to adapter of AutoCompleteTextView as doing so doesn’t require much resource.

But if there is a plenty of data that need to be searched against, it is not good idea to keep entire data in memory. The best way to handle this scenario is searching and getting data from database as user enters text and show auto complete suggestion.

In this post, you can learn dynamically populating AutoCompleteTextView suggestions drop down by reading data from database using room and SQLite databse.

To learn more about AutoCompleteTextView, you can read AutoCompleteTextView tutorial, populating auto complete dropdown with data from web services, and AutoCompleteTextView custom layout and adapter.

If you are new to Room persistency framework, you can see Android Room and steps to use Room, Android Room example, and database migration with Room.

AutoCompleteTextView Dynamic Serach Example Dependency

Since we are using room to read and write data to SQLite databse, we need to add below room libraries to gradle build file.

dependencies {
    . . .
    implementation 'android.arch.persistence.room:runtime:1.0.0-beta2'
    annotationProcessor 'android.arch.persistence.room:compiler:1.0.0-beta2'
}

AutoCompleteTextView Dynamic Serach Example Output

android autocompletetextview dynamic search, custom layout and adapter example

AutoCompleteTextView Dropdown Layout

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

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/store"
        style="?android:attr/dropDownItemStyle"
        android:layout_width="200dp"
        android:layout_height="?android:attr/listPreferredItemHeight"
        android:layout_marginLeft="8dp"
        android:ellipsize="marquee"
        android:singleLine="true"
        android:textAppearance="?android:attr/textAppearanceLargePopupMenu"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/coupon"
        style="?android:attr/dropDownItemStyle"
        android:layout_width="100dp"
        android:layout_height="?android:attr/listPreferredItemHeight"
        android:layout_marginRight="8dp"
        android:ellipsize="marquee"
        android:singleLine="true"
        android:textAppearance="?android:attr/textAppearanceLargePopupMenu"
        app:layout_constraintHorizontal_bias="0.809"
        app:layout_constraintLeft_toRightOf="@+id/store"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout> 

AutoCompleteTextView Adapter

In addition to getView method implementation, we need to define a Filter class in our custom adapter. AutoCompleteTextView calls performFiltering method passing user entered search string to it. In this method, we use Room DAO to get list of matching records from database for the search string.

import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import zoftino.com.uicontrols.database.LocalDatabaseRepo;
import zoftino.com.uicontrols.database.StoreOffers;

public class StoreListAdapter extends ArrayAdapter {

    private List<StoreOffers> dataList;
    private Context mContext;
    private int itemLayout;

    private LocalDatabaseRepo localDatabaseRepo = new LocalDatabaseRepo();

    private StoreListAdapter.ListFilter listFilter = new StoreListAdapter.ListFilter();

    public StoreListAdapter(Context context, int resource, List<StoreOffers> storeDataLst) {
        super(context, resource, storeDataLst);
        dataList = storeDataLst;
        mContext = context;
        itemLayout = resource;
    }

    @Override
    public int getCount() {
        return dataList.size();
    }

    @Override
    public StoreOffers getItem(int position) {
        return dataList.get(position);
    }

    @Override
    public View getView(int position, View view, @NonNull ViewGroup parent) {

        if (view == null) {
            view = LayoutInflater.from(parent.getContext())
                    .inflate(itemLayout, parent, false);
        }

        TextView strName = (TextView) view.findViewById(R.id.store);
        strName.setText(getItem(position).getStoreName());

        TextView couponCount = (TextView) view.findViewById(R.id.coupon);
        couponCount.setText(""+getItem(position).getCouponsCount());

        return view;
    }

    @NonNull
    @Override
    public Filter getFilter() {
        return listFilter;
    }

    public class ListFilter extends Filter {
        private Object lock = new Object();

        @Override
        protected FilterResults performFiltering(CharSequence prefix) {
            FilterResults results = new FilterResults();

            if (prefix == null || prefix.length() == 0) {
                synchronized (lock) {
                    results.values = new ArrayList<String>();
                    results.count = 0;
                }
            } else {
                final String searchStrLowerCase = prefix.toString().toLowerCase();

                //Call to database to get matching records using room
                List<StoreOffers> matchValues =
                        localDatabaseRepo.getStoreInfo(mContext, searchStrLowerCase);

                results.values = matchValues;
                results.count = matchValues.size();
            }

            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (results.values != null) {
                dataList = (ArrayList<StoreOffers>)results.values;
            } else {
                dataList = null;
            }
            if (results.count > 0) {
                notifyDataSetChanged();
            } else {
                notifyDataSetInvalidated();
            }
        }

    }
}

Activity

package zoftino.com.uicontrols;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

import zoftino.com.uicontrols.database.StoreOffers;


public class DynamicSearchDatabaseActivity extends AppCompatActivity {

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

        AutoCompleteTextView nameTV = (AutoCompleteTextView)
                findViewById(R.id.store);

        List<StoreOffers> storeOffers = new ArrayList<StoreOffers>();
        StoreListAdapter adapter = new StoreListAdapter(this,
                R.layout.store_item, storeOffers);


        nameTV.setAdapter(adapter);
        nameTV.setOnItemClickListener(onItemClickListener);
    }

    private AdapterView.OnItemClickListener onItemClickListener =
            new AdapterView.OnItemClickListener(){
                @Override
                public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {

                    Toast.makeText(DynamicSearchDatabaseActivity.this,
                            "Clicked item from auto completion list "
                                    + adapterView.getItemAtPosition(i)
                            , Toast.LENGTH_SHORT).show();
                }
            };
}

Activity Layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android.support.design="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.TextInputLayout
    android:id="@+id/store_til"
    android:layout_width="300dp"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp"
    android:layout_marginEnd="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="16dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">
    <AutoCompleteTextView
        android:id="@+id/store"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Enter Store">
    </AutoCompleteTextView>
</android.support.design.widget.TextInputLayout>
</android.support.constraint.ConstraintLayout>

Room Database

 package zoftino.com.uicontrols.database;

import android.arch.persistence.room.Database;
import android.arch.persistence.room.RoomDatabase;

@Database(entities = {StoreOffers.class}, version = 1)
public abstract class StoreInfoDatabase extends RoomDatabase {
    public abstract StoreInfoDAO storeInfoDAO();
}

Entity

 package zoftino.com.uicontrols.database;


import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;

@Entity
public class StoreOffers {
    @PrimaryKey(autoGenerate = true)
    private int id;

    String storeName;
    int couponsCount;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getStoreName() {
        return storeName;
    }

    public void setStoreName(String storeName) {
        this.storeName = storeName;
    }

    public int getCouponsCount() {
        return couponsCount;
    }

    public void setCouponsCount(int couponsCount) {
        this.couponsCount = couponsCount;
    }
} 

Room DAO

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Query;

import java.util.List;

@Dao
public interface StoreInfoDAO {
    @Query("SELECT * FROM StoreOffers WHERE storeName like :storeIn ")
    List<StoreOffers> getStores(String storeIn);
}

Repository

package zoftino.com.uicontrols.database;

import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.content.ContentValues;
import android.content.Context;

import java.util.List;

public class LocalDatabaseRepo {
    private static StoreInfoDatabase storeInfoDatabase;
    private StoreInfoDAO storeInfoDAO;
    private static final Object LOCK = new Object();

    private static RoomDatabase.Callback dbCallback = new RoomDatabase.Callback(){
        public void onCreate (SupportSQLiteDatabase db){

        }
        public void onOpen (SupportSQLiteDatabase db){
            //delete existing data
            db.execSQL("Delete From StoreOffers");

            ContentValues contentValues = new ContentValues();
            contentValues.put("storeName", "Amazon");
            contentValues.put("couponsCount", 30);
            db.insert("StoreOffers", OnConflictStrategy.IGNORE, contentValues);

            contentValues = new ContentValues();
            contentValues.put("storeName", "AmPM");
            contentValues.put("couponsCount", 13);
            db.insert("StoreOffers", OnConflictStrategy.IGNORE, contentValues);

            contentValues = new ContentValues();
            contentValues.put("storeName", "BestFashion");
            contentValues.put("couponsCount", 11);
            db.insert("StoreOffers", OnConflictStrategy.IGNORE, contentValues);

            contentValues = new ContentValues();
            contentValues.put("storeName", "FashionStore");
            contentValues.put("couponsCount", 53);
            db.insert("StoreOffers", OnConflictStrategy.IGNORE, contentValues);

            contentValues = new ContentValues();
            contentValues.put("storeName", "FashionMall");
            contentValues.put("couponsCount", 43);
            db.insert("StoreOffers", OnConflictStrategy.IGNORE, contentValues);

            contentValues = new ContentValues();
            contentValues.put("storeName", "FashionSale");
            contentValues.put("couponsCount", 23);
            db.insert("StoreOffers", OnConflictStrategy.IGNORE, contentValues);
        }
    };

    public synchronized static StoreInfoDatabase getStoreInfoDatabase(Context context){
        if(storeInfoDatabase == null) {
            synchronized (LOCK) {
                if (storeInfoDatabase == null) {
                    storeInfoDatabase = Room.databaseBuilder(context,
                            StoreInfoDatabase.class, "store info db")
                                                         .addCallback(dbCallback).build();
                }
            }
        }
        return storeInfoDatabase;
    }


    public List<StoreOffers> getStoreInfo(Context context, String storeStr) {
        if (storeInfoDAO == null) {
            storeInfoDAO = LocalDatabaseRepo.getStoreInfoDatabase(context).storeInfoDAO();
        }
        return storeInfoDAO.getStores(storeStr+"%");
    }
}