I assume the reader to have basic android programming skills. This post will cover the making of a simple android contact picker with the following features
Step by step guide
Step 1: Get required permission in the manifest file
<uses-permission android:name="android.permission.READ_CONTACTS" />
This should be written outside the application tag.
Step 2: Set up the contacts picker activity
The contacts picker activity will display a list of contacts and a search box, which will enable the end-user to select multiple contacts.
The following is the layout file for the activity
<?xml version="1.0" encoding="utf-8"?>
<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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="sachinmuralig.me.simplemulticontactpicker.ContactsPickerActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txt_filter"
android:hint="@string/txt_search"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txt_load_progress"
android:layout_below="@+id/txt_filter"
android:text="Loading..."
/>
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_load_progress"
android:layout_above="@+id/btn_done"
android:id="@+id/lst_contacts_chooser"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btn_done"
android:layout_alignParentBottom="true"
android:text="@string/txt_done"
/>
</RelativeLayout>
The layout is pretty straight forward - an EditText for the search input, a ListView for contacts display and a 'Done' Button.
Step 3 : Represent a Contact as a class
package sachinmuralig.me.simplemulticontactpicker;
import android.os.Parcel;
import android.os.Parcelable;
public class Contact implements Parcelable {
public String id,name,phone,label;
Contact(String id, String name,String phone,String label){
this.id=id;
this.name=name;
this.phone=phone;
this.label=label;
}
protected Contact(Parcel in) {
id = in.readString();
name = in.readString();
phone = in.readString();
label = in.readString();
}
public static final Creator<Contact> CREATOR = new Creator<Contact>() {
@Override
public Contact createFromParcel(Parcel in) {
return new Contact(in);
}
@Override
public Contact[] newArray(int size) {
return new Contact[size];
}
};
@Override
public String toString()
{
return name+" | "+label+" : "+phone;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(name);
dest.writeString(phone);
dest.writeString(label);
}
}
The Class implements Parcelable so that the selected contacts can be easily returned to another activity.
Read more about parcelable here
Not that the toString() method was overridden to return a presentable view of the contact data. The contact data that is used here is minimal(id, name, phone, label) to keep it simple and the data members can be easily populated using the Constructor.
Step 4 : The Contacts List Class
This adds another level of abstraction. We will be basically working with Contacts List. An instance of contacts list will contain all the contacts that is in the phone, another contacts list will hold the filtered contacts, which are filtered according to the search input and a third contact list will contain the contacts selected by the end-user, and this third list will be returned as the result.
package sachinmuralig.me.simplemulticontactpicker;
import java.util.ArrayList;
public class ContactsList{
public ArrayList<Contact> contactArrayList;
ContactsList(){
contactArrayList = new ArrayList<Contact>();
}
public int getCount(){
return contactArrayList.size();
}
public void addContact(Contact contact){
contactArrayList.add(contact);
}
public void removeContact(Contact contact){
contactArrayList.remove(contact);
}
public Contact getContact(int id){
for(int i=0;i<this.getCount();i++){
if(Integer.parseInt(contactArrayList.get(i).id)==id)
return contactArrayList.get(i);
}
return null;
}
}
An
ArrayList will hold the instances of Contact class and the methods aid in manipulating this list.
Step 5: Contacts List Adapter
The ContactsListAdapter will serve as the adapter for the contacts listview in our activity.
package sachinmuralig.me.simplemulticontactpicker;
import android.app.Activity;
import android.content.Context;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import java.util.ArrayList;
public class ContactsListAdapter extends BaseAdapter {
Context context;
ContactsList contactsList,filteredContactsList,selectedContactsList;
String filterContactName;
ContactsListAdapter(Context context, ContactsList contactsList){
super();
this.context = context;
this.contactsList = contactsList;
this.filteredContactsList=new ContactsList();
this.selectedContactsList = new ContactsList();
this.filterContactName = "";
}
public void filter(String filterContactName){
filteredContactsList.contactArrayList.clear();
if(filterContactName.isEmpty() || filterContactName.length()<1){
filteredContactsList.contactArrayList.addAll(contactsList.contactArrayList);
this.filterContactName = "";
}
else {
this.filterContactName = filterContactName.toLowerCase().trim();
for (int i = 0; i < contactsList.contactArrayList.size(); i++) {
if (contactsList.contactArrayList.get(i).name.toLowerCase().contains(filterContactName))
filteredContactsList.addContact(contactsList.contactArrayList.get(i));
}
}
notifyDataSetChanged();
}
public void addContacts(ArrayList<Contact> contacts){
this.contactsList.contactArrayList.addAll(contacts);
this.filter(this.filterContactName);
}
@Override
public int getCount() {
return filteredContactsList.getCount();
}
@Override
public Contact getItem(int position) {
return filteredContactsList.contactArrayList.get(position);
}
@Override
public long getItemId(int position) {
return Long.parseLong(this.getItem(position).id);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if(convertView==null){
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
convertView = inflater.inflate(R.layout.contact_item, parent, false);
viewHolder = new ViewHolder();
viewHolder.chkContact = (CheckBox) convertView.findViewById(R.id.chk_contact);
convertView.setTag(viewHolder);
}else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.chkContact.setText(this.filteredContactsList.contactArrayList.get(position).toString());
viewHolder.chkContact.setId(Integer.parseInt(this.filteredContactsList.contactArrayList.get(position).id));
viewHolder.chkContact.setChecked(alreadySelected(filteredContactsList.contactArrayList.get(position)));
viewHolder.chkContact.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Contact contact = filteredContactsList.getContact(buttonView.getId());
if(contact!=null && isChecked && !alreadySelected(contact)){
selectedContactsList.addContact(contact);
}
else if(contact!=null && !isChecked){
selectedContactsList.removeContact(contact);
}
}
});
return convertView;
}
public boolean alreadySelected(Contact contact)
{
if(this.selectedContactsList.getContact(Integer.parseInt(contact.id))!=null)
return true;
return false;
}
public static class ViewHolder{
CheckBox chkContact;
}
}
contactsList - will hold the entire contacts in the device, filteredContactsList will hold a subset of the contactsList and is filtered based on the contact name, selectedContactsList also contains a subset of contactsList and this holds the contacts that are selected from the list using the checkbox.
The getView method inflates a layout that basically just contains a checkbox, for each contact that is to be displayed. You can modify this, if you want to add other fields like email or contact image etc.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<CheckBox
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/chk_contact"
/>
</LinearLayout>
The 'filter' method is the one performing the filter actions and displaying the filtered contacts.
Step 6: Loading the contacts Asynchronously
The contactsList in the Adapter must be populated in a separate thread. An AsyncTask is used for the same.
package sachinmuralig.me.simplemulticontactpicker;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.ContactsContract;
import android.view.View;
import android.widget.TextView;
import java.util.ArrayList;
public class ContactsLoader extends AsyncTask<String,Void,Void> {
ContactsListAdapter contactsListAdapter;
Context context;
private ArrayList<Contact> tempContactHolder;
TextView txtProgress;
int totalContactsCount,loadedContactsCount;
ContactsLoader(Context context,ContactsListAdapter contactsListAdapter){
this.context = context;
this.contactsListAdapter = contactsListAdapter;
this.tempContactHolder= new ArrayList<>();
loadedContactsCount=0;
totalContactsCount=0;
txtProgress=null;
}
@Override
protected Void doInBackground(String[] filters) {
String filter = filters[0];
ContentResolver contentResolver = context.getContentResolver();
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String[] projection = new String[]{
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER
};
Cursor cursor;
if(filter.length()>0) {
cursor = contentResolver.query(
uri,
projection,
ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?",
new String[]{filter},
ContactsContract.Contacts.DISPLAY_NAME + " ASC"
);
}else {
cursor = contentResolver.query(
uri,
projection,
null,
null,
ContactsContract.Contacts.DISPLAY_NAME + " ASC"
);
}
totalContactsCount = cursor.getCount();
if(cursor!=null && cursor.getCount()>0){
while(cursor.moveToNext()) {
if (Integer.parseInt(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
Cursor phoneCursor = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?",
new String[]{id},
null
);
if (phoneCursor != null && phoneCursor.getCount() > 0) {
while (phoneCursor.moveToNext()) {
String phId = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID));
String customLabel = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL));
String label = (String) ContactsContract.CommonDataKinds.Phone.getTypeLabel(context.getResources(),
phoneCursor.getInt(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE)),
customLabel
);
String phNo = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
tempContactHolder.add(new Contact(phId, name, phNo, label));
}
phoneCursor.close();
}
}
loadedContactsCount++;
publishProgress();
}
cursor.close();
}
return null;
}
@Override
protected void onProgressUpdate(Void[] v){
if(this.tempContactHolder.size()>=100) {
contactsListAdapter.addContacts(tempContactHolder);
this.tempContactHolder.clear();
if(txtProgress!=null){
txtProgress.setVisibility(View.VISIBLE);
String progressMessage = "Loading...("+loadedContactsCount+"/"+totalContactsCount+")";
txtProgress.setText(progressMessage);
}
}
}
@Override
protected void onPostExecute(Void v){
contactsListAdapter.addContacts(tempContactHolder);
tempContactHolder.clear();
if(txtProgress!=null) {
txtProgress.setText("");
txtProgress.setVisibility(View.GONE);
}
}
}
A cursor is used to get the contacts with phone number and each contact is exploded, if it has multiple phone numbers. A buffer tempContactHolder is used to get a set of contacts at a time and then populate the adapter's contactsList using the adapter's addContacts() method. The addContacts() will update the contactsList, apply any existing filters and then update the ListView.
Step 7: Initialize the Listview, Search textbox, 'Done' Button and load contacts in the ContactPickerActivity
package sachinmuralig.me.simplemulticontactpicker;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
public class ContactsPickerActivity extends AppCompatActivity {
ListView contactsChooser;
Button btnDone;
EditText txtFilter;
TextView txtLoadInfo;
ContactsListAdapter contactsListAdapter;
ContactsLoader contactsLoader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contacts_picker);
contactsChooser = (ListView) findViewById(R.id.lst_contacts_chooser);
btnDone = (Button) findViewById(R.id.btn_done);
txtFilter = (EditText) findViewById(R.id.txt_filter);
txtLoadInfo = (TextView) findViewById(R.id.txt_load_progress);
contactsListAdapter = new ContactsListAdapter(this,new ContactsList());
contactsChooser.setAdapter(contactsListAdapter);
loadContacts("");
txtFilter.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
contactsListAdapter.filter(s.toString());
}
@Override
public void afterTextChanged(Editable s) {
}
});
btnDone.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(contactsListAdapter.selectedContactsList.contactArrayList.isEmpty()){
setResult(RESULT_CANCELED);
}
else{
Intent resultIntent = new Intent();
resultIntent.putParcelableArrayListExtra("SelectedContacts", contactsListAdapter.selectedContactsList.contactArrayList);
setResult(RESULT_OK,resultIntent);
}
finish();
}
});
}
private void loadContacts(String filter){
if(contactsLoader!=null && contactsLoader.getStatus()!= AsyncTask.Status.FINISHED){
try{
contactsLoader.cancel(true);
}catch (Exception e){
}
}
if(filter==null) filter="";
try{
//Running AsyncLoader with adapter and filter
contactsLoader = new ContactsLoader(this,contactsListAdapter);
contactsLoader.txtProgress = txtLoadInfo;
contactsLoader.execute(filter);
}catch(Exception e){
e.printStackTrace();
}
}
}
Note that the search textbox has a TextWatcher attached to it, that will call the filter method of the adapter on text change. The loadContacts() method will load all contacts from device and populate the adapter data members. The 'Done' button, on click will set the intent data as the selected contact list and will return the contacts to the activity which invoked startActivityForResult.
Step 8: Read the contacts on Activity result
From the Activity which invoked the ContactPickerActivity, you can read the selected contacts from onActivityResult. A sample implementation is as follows. (See MainActivity in the github project)
@Override
public void onActivityResult(int requestCode,int resultCode,Intent data){
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == CONTACT_PICK_REQUEST && resultCode == RESULT_OK){
ArrayList<Contact> selectedContacts = data.getParcelableArrayListExtra("SelectedContacts");
String display="";
for(int i=0;i<selectedContacts.size();i++){
display += (i+1)+". "+selectedContacts.get(i).toString()+"\n";
}
contactsDisplay.setText("Selected Contacts : \n\n"+display);
}
}