In this article we are going to learn how to add search filter functionality to RecyclerView. Adding search is very simple task, we’ll use Toolbar’s search widget to input the search query. To demonstrate I am taking an example of contacts list and search for a contact by name or phone number or MailId
1. RecyclerView Search Filter – getFilter()
Android provides Filterable class to filter the data by a filter (condition). Usually the getFilter() method has to be overridden in the adapter class in which the filter condition is provided to search through a list. Below is an example of getFilter() method to search a contact by name or phone number from a list of contacts.
override fun getFilter(): Filter { return object : Filter() { override fun performFiltering(charSequence: CharSequence): FilterResults { val charString = charSequence.toString() if (charString.isEmpty()) { contactListFiltered = mMaincontactsList } else { val filteredList = ArrayList<ContactVO>() for (row in mMaincontactsList) { // name match condition. this might differ depending on your requirement // here we are looking for name or phone number match if (row.contactName.toLowerCase().contains(charString.toLowerCase()) || row.contactEmail.toLowerCase().contains(charString.toLowerCase()) || row.contactNumber.contains(charSequence) ) { filteredList.add(row) } } contactListFiltered = filteredList } val filterResults = FilterResults() filterResults.values = contactListFiltered return filterResults } override fun publishResults(charSequence: CharSequence, filterResults: FilterResults) { contactListFiltered = filterResults.values as ArrayList<ContactVO> notifyDataSetChanged() } } } |
2. Creating New Project
1. Create the MainActivity.kt and add the code as shown below..
package kotlin.com.contactssearch import android.Manifest import android.app.AlertDialog import android.content.DialogInterface import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.provider.ContactsContract import android.text.Editable import android.text.TextWatcher import android.util.Log import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { private val contactVOList: ArrayList<ContactVO> = ArrayList() private var mContactsRecyclerView: RecyclerView? = null private var mSearchContactsAdapter: ContactsAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mContactsRecyclerView = contacts_recyclerView if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { readContacts() } else { requestReadContactsPermission() } /** * Enabling Search Filter * */ search_editText.addTextChangedListener(object : TextWatcher { override fun onTextChanged(cs: CharSequence, arg1: Int, arg2: Int, arg3: Int) { mSearchContactsAdapter!!.getFilter().filter(cs.toString().trim()) } override fun beforeTextChanged(arg0: CharSequence, arg1: Int, arg2: Int, arg3: Int) { } override fun afterTextChanged(arg0: Editable) { } }) } private fun readContacts() { val cr = this.contentResolver val cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null) if (cursor!!.getCount() > 0) { while (cursor.moveToNext()) { val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID)) val name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)) if (Integer.parseInt(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) { val mContactVO = ContactVO() mContactVO.contactName = name // get the phone number val phoneCursor = cr.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", arrayOf<String>(id), null ) while (phoneCursor!!.moveToNext()) { val phone = phoneCursor.getString( phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) ) Log.i("phone : => ", phone) mContactVO.contactNumber = phone } phoneCursor.close() // get email and type val emailCursor = cr.query( ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?", arrayOf<String>(id), null ) while (emailCursor!!.moveToNext()) { // This would allow you get several email addresses // if the email addresses were stored in an array val email = emailCursor.getString( emailCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA) ) val emailType = emailCursor.getString( emailCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.TYPE) ) mContactVO.contactEmail = email } emailCursor.close() contactVOList.add(mContactVO) } } //Contacts list val layoutManager = LinearLayoutManager(this) contacts_recyclerView!!.layoutManager = (layoutManager) mSearchContactsAdapter = ContactsAdapter(this, contactVOList) contacts_recyclerView!!.adapter = mSearchContactsAdaptermSearchContactsAdapter!!.notifyDataSetChanged() } } private fun requestReadContactsPermission() { if (ActivityCompat.shouldShowRequestPermissionRationale( this, android.Manifest.permission.READ_CONTACTS ) ) { // show UI part if you want here to show some rationale !!! } else { ActivityCompat.requestPermissions( this, arrayOf(android.Manifest.permission.READ_CONTACTS), REQUEST_READ_CONTACTS ) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { when (requestCode) { REQUEST_READ_CONTACTS -> { if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { readContacts() } else { if (ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.READ_CONTACTS ) ) { // now, user has denied permission (but not permanently!) } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) { showMessageOKCancel("You need to allow access to both the permissions", DialogInterface.OnClickListener { dialog, which -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestReadContactsPermission() } }) return } } } } return } } } private fun showMessageOKCancel(message: String, okListener: DialogInterface.OnClickListener) { AlertDialog.Builder(this) .setMessage(message) .setPositiveButton("OK", okListener) .setNegativeButton("Cancel", null) .create() .show() } companion object { private var REQUEST_READ_CONTACTS: Int = 1 } } |
2. Create the ContactsAdapter.kt and add the code as shown below.
package kotlin.com.contactssearch import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Filter import android.widget.Filterable import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.item_contacts.view.* class ContactsAdapter( private val context: Context, private var mMaincontactsList: ArrayList<ContactVO>) : RecyclerView.Adapter<ContactsAdapter.ViewHolder>(), Filterable { private var contactListFiltered: List<ContactVO>? = mMaincontactsList override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_contacts, parent, false)) } override fun getItemCount(): Int { return contactListFiltered!!.size } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.contactName?.text = contactListFiltered!![position].contactName holder.contactEmail?.text = contactListFiltered!![position].contactNumber } class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val contactName = view.name_textView val contactEmail = view.email_textView } override fun getFilter(): Filter { return object : Filter() { override fun performFiltering(charSequence: CharSequence): FilterResults { val charString = charSequence.toString() if (charString.isEmpty()) { contactListFiltered = mMaincontactsList } else { val filteredList = ArrayList<ContactVO>() for (row in mMaincontactsList) { // name match condition. this might differ depending on your requirement // here we are looking for name or phone number match if (row.contactName.toLowerCase().contains(charString.toLowerCase()) || row.contactEmail.toLowerCase().contains(charString.toLowerCase()) || row.contactNumber.contains(charSequence) ) { filteredList.add(row) } } contactListFiltered = filteredList } val filterResults = FilterResults() filterResults.values = contactListFiltered return filterResults } override fun publishResults(charSequence: CharSequence, filterResults: FilterResults) { contactListFiltered = filterResults.values as ArrayList<ContactVO> notifyDataSetChanged() } } } } |
3. Open build.gradle under app folder and add Androidx, RecyclerView dependencies.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "androidx.core:core-ktx:$rootProject.androidXCoreVersion"
implementation "androidx.appcompat:appcompat:$rootProject.androidXVersion" implementation "androidx.recyclerview:recyclerview:$rootProject.androidXVersion" implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutXVersion" implementation "com.google.android.material:material:$rootProject.androidXVersion"
testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'}
4. Add the below resources to respective strings.xml, dimens.xml, colors.xml files.
<resources>
<string name="app_name">ContactsSearch</string>
</resources>
<?xml version="1.0" encoding="utf-8"?><resources>
<!--Margin Values--> <dimen name="margin_200dp">200dp</dimen>
<dimen name="margin_85dp">85dp</dimen>
<dimen name="margin_60dp">60dp</dimen>
<dimen name="margin_50dp">50dp</dimen>
<dimen name="margin_40dp">40dp</dimen>
<dimen name="margin_20dp">20dp</dimen>
<dimen name="margin_30dp">30dp</dimen>
<dimen name="margin_10dp">10dp</dimen>
<dimen name="margin_5dp">5dp</dimen>
<dimen name="margin_2dp">2dp</dimen>
<!--Padding Values--> <dimen name="padding_90dp">90dp</dimen>
<dimen name="padding_80dp">80dp</dimen>
<dimen name="padding_85dp">85dp</dimen>
<dimen name="padding_60dp">60dp</dimen>
<dimen name="padding_40dp">40dp</dimen>
<dimen name="padding_10dp">10dp</dimen>
<dimen name="padding_5dp">5dp</dimen>
<!--Text Size--> <dimen name="textsize_40sp">40sp</dimen>
<dimen name="textsize_30sp">30sp</dimen>
<dimen name="textsize_24sp">24sp</dimen>
<dimen name="textsize_20sp">20sp</dimen>
<dimen name="textsize_16sp">16sp</dimen>
<dimen name="textsize_15sp">15sp</dimen>
<dimen name="textsize_14sp">14sp</dimen>
<dimen name="textsize_10sp">10sp</dimen>
<!--Text Spacing--> <dimen name="payment_description_text_spacing">3dp</dimen>
<!--menu drawer--> <dimen name="drawer_menu_profile_width">99dp</dimen>
<dimen name="drawer_menu_profile_height">99dp</dimen>
<dimen name="drawer_menu_width">340dp</dimen>
<dimen name="menu_text_padding">8dp</dimen>
<dimen name="drawer_padding_top">30dp</dimen>
<dimen name="drawer_padding_left">20dp</dimen>
<dimen name="menu_home_margin">50dp</dimen>
<dimen name="user_profile_name_text_size">24sp</dimen>
<dimen name="user_name_text_size">16sp</dimen>
<dimen name="menu_text_size">22sp</dimen>
<dimen name="menu_bottom_text_size">12sp</dimen>
<dimen name="line_spacing_extra">2sp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?><resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>
5. Open AndroidManifest.xml and add Also add the READ_CONTACTS permission as we are going to get all contacts
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="kotlin.com.contactssearch">
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
6. As we need to parse the Contacts, we need a POJO class to serialize the json. Create a class named ContactVO.kt and add name, image and phone number.
package kotlin.com.contactssearch
class ContactVO() {
var contactName = "" var contactNumber = "" var contactEmail = ""}
7. Open the layout files of main activity activity_main.xml and item_contacts.xml add RecyclerView element.
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent">
<EditText android:id="@+id/search_editText" android:layout_width="match_parent" android:layout_height="wrap_content" android:cursorVisible="true" android:focusable="true" android:singleLine="true" android:imeOptions="actionDone" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/>
<androidx.recyclerview.widget.RecyclerView android:id="@+id/contacts_recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@+id/search_editText" app:layout_constraintStart_toStartOf="parent">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_marginStart="@dimen/margin_10dp" android:layout_width="match_parent" android:padding="@dimen/padding_10dp" android:layout_height="wrap_content">
<ImageView android:id="@+id/profile_imageView" android:layout_width="40dp" android:layout_height="40dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" android:src="@drawable/profile_placeholder"/>
<TextView android:id="@+id/name_textView" android:layout_width="wrap_content" android:textSize="@dimen/textsize_16sp" android:paddingStart="@dimen/margin_10dp" app:layout_constraintLeft_toRightOf="@+id/profile_imageView" android:textColor="@android:color/black" app:layout_constraintTop_toTopOf="parent" android:textStyle="bold" android:layout_height="wrap_content"/>
<TextView android:id="@+id/email_textView" android:layout_width="wrap_content" android:textSize="@dimen/textsize_14sp" android:paddingStart="@dimen/margin_10dp" app:layout_constraintTop_toBottomOf="@+id/name_textView" app:layout_constraintLeft_toRightOf="@+id/profile_imageView" android:textColor="@android:color/black" android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
No comments:
Post a Comment