Rxjava split getting contacts - android

I need to split getting contacts from phone's contact list database. If user has a lot of contacts loading stucks for few seconds to load what is not very good. I want to divide it with Rx to load 20 contacts a time, but with loading all contacts on start, not with lazy loading. At this moment i tried it with pull of observables with concat, but it collects all the results and return after finishing all of them instead of returning every 20 contacts after getting them and just after returning continue with next pack of contacts. What is wrong with this implementation?
#Override public Observable<List<Contact>> getPhoneContacts() {
return Observable.fromCallable(this::getCursor)
.concatMap(cursor -> {
List<Observable<List<Contact>>> list = new ArrayList<>();
int pagesCount = cursor.getCount()/20 + 1;
for(int i = 0; i < pagesCount; i++){
list.add(Observable.just(getContactList(cursor)));
}
return Observable.concat(list).compose(upstream -> {
cursor.close();
return upstream;
});
});
}
private List<Contact> getContactList(Cursor cursor) {
List<Contact> contacts = new ArrayList<>();
if (cursor != null && !cursor.isClosed() && areContactsPresent(cursor)) {
while (contacts.size() < 20){
if (cursor.moveToNext()) {
String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
Contact contact = getContact(cursor, id);
if (contact != null) {
contacts.add(contact);
}
}
//else {
// cursor.close();
// break;
//}
}
}
return contacts;
}
private Cursor getCursor(){
return contentResolver.query(ContactsContract.Contacts.CONTENT_URI,
null, ContactsContract.Data.HAS_PHONE_NUMBER+">0", null, ContactsContract.Contacts.DISPLAY_NAME + " ASC");
}
private Contact getContact(Cursor cursor, String id) {
Contact contact = null;
Cursor phonesCursor = contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
new String[]{id}, null);
if (phonesCursor != null) {
while (phonesCursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex(
ContactsContract.Contacts.DISPLAY_NAME));
String phoneNo = phonesCursor.getString(
phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
if (!phoneNo.isEmpty()) {
contact = Contact.create(name, phoneNo);
}
}
phonesCursor.close();
}
return contact;
}

You source observable needs to look something like that
Observable.create((ObservableOnSubscribe<List<Contact>>) emitter -> {
int pagesCount = cursor.getCount()/20 + 1;
for(int i = 0; i < pagesCount; i++){
emitter.onNext(getContactList(cursor)); // Passes list with 20 items to onNext()
}
emitter.onComplete();
});
Your data source now returns List<Contact> instead of a single Contact, which means each emission will provide you with a list of contacts, each list has 20 items as per your implementation.
Then you can update UI with each emission, instead of waiting for all of the contacts to load.

Related

Read contacts from phone is very slow

I am trying to get all contact from my phone, including get all numbers from contacts with multiple numbers.
So i've build query that while not over run all over contacts, and build Contact user, and have inside query with id selection to get all numbers for each user. but since my inside query is including selection it takes a long time. any other idea?
private Cursor initPhoneCursor() {
try {
// get the contacts URI
final Uri phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
// get the name column's name depending on the Android Version
final String nameColumn = Contact.COLUMN_NAME_PHONE;
// declare columns object - init later depending on version
String selection = getQuerySelectionForCursor();
String[] columns = getColumnSelectionForCursor(nameColumn);
if (mApp != null) {
// return cursor from contentresolver
return mApp.getContentResolver().query(phoneUri, columns, selection, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC");
}
} catch (Exception e) {
// couldn't read phone cursor
CaughtExceptionHandler.reportException(e);
}
return null;
}
private void importContactsFromCursor(Cursor cursor, boolean isSimCard) {
mCurrentContactCursor = initPhoneCursor();
// check cursor is alive
if (cursor != null && !cursor.isClosed()) {
while (cursor.moveToNext() && shouldContinueImport()) {
// // as log as we have contacts, move through them
importContact(cursor, isSimCard);
mCurrentContact++;
}
// when done - close the cursor
cursor.close();
}
}
private void importContact(Cursor cursor, boolean simCard) {
// create Contact object
Contact row = new Contact(cursor, simCard);
// mContactsTimer.onContactCreated();
if (simCard) {
// if simCard, contact must have number
// validate number and create contact
row = validateAndCheckNumber(row, cursor);
}
else {
// if not sim card (phone cursor), a contact might have no numbers,
// single or multiple phone numberss
// let's check if this contact has any numbers
if (hasPhoneNumbers(cursor)) {
// get all of the contact's phone numbers
row = importAllNumbersForContact(row);
}
}
// check if this is valid
final boolean isValidForSaving = row != null && row.hasName() && row.hasNumbers();
if (isValidForSaving && !sStopRequested) {
mContactsToSave.add(row);
}
}
private Contact importAllNumbersForContact(Contact contact) {
// uri of contact phones
Uri contentUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
// contact_id = ?
String selection = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?";
String[] selectionArgs = {String.valueOf(contact.getOriginalId())};
// do the query
Cursor phoneCursor = mApp.getContentResolver().query(contentUri, null, selection, selectionArgs, null);
if (phoneCursor != null) {
// save numbers if we got anything
contact = loopThroughContactNumbers(contact, phoneCursor);
// close cursor when done
phoneCursor.close();
}
return contact;
}
Go with the following solution:
Map<String,Contact> contactsMap = new TreeMap<>();
contacts = new ArrayList<>();
Cursor phones = getBaseContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,null,null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME+" ASC");
assert phones != null;
while (phones.moveToNext())
{
Contact contact = new Contact();
contact.setDisplayName(phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
contact.setPhoneNumber(phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
contact.setDisplayPicture(phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.PHOTO_THUMBNAIL_URI)));
contactsMap.put(phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)),contact);
}
contacts.addAll(contactsMap.values());
phones.close();
Modify it for all numbers of a contact. You are good to go with.

How to return all value from a function

I want to return all values of dbManagement.ORIGINATING_ADDRESS from this function but it just gives me last one value to blockedNumber. I know it'll give last one value but how can i get all value. kindly help
public String selectBlockedNumbers() {
Cursor cursor = dbManagement.selectBlockedNumbers();
String blockedNumber = null;
cursor.moveToFirst();
for(int i = 0; i < cursor.getCount(); i++) {
blockedNumber = cursor.getString(cursor.getColumnIndex(dbManagement.ORIGINATING_ADDRESS));
cursor.moveToNext();
}
Toast.makeText(this, blockedNumber, Toast.LENGTH_LONG).show();
return blockedNumber;
}
In
Cursor cursor = dbManagement.selectBlockedNumbers();
the function selectBlockedNumbers() consist of following query:
cursor = db.rawQuery("SELECT " + ORIGINATING_ADDRESS + " FROM " + TABLE_BLOCK_LIST, null);
When the for-loop runs for the last time, blockedNumbers will contain the last number retrieved. So, instead of returning a String, rather returns a List<String>. In that case your code should change into
public List<String> selectBlockedNumbers() {
Cursor cursor = dbManagement.selectBlockedNumbers();
List<String> blockedNumbers = new ArrayList<String>();
cursor.moveToFirst();
for(int i = 0; i < cursor.getCount(); i++) {
String blockedNumber = cursor.getString(cursor.getColumnIndex(dbManagement.ORIGINATING_ADDRESS));
blockedNumbers.add(blockedNumber);
cursor.moveToNext();
}
Toast.makeText(this, blockedNumber, Toast.LENGTH_LONG).show();
return blockedNumbers;
}

How to get last inserted contact number from the phone book android?

How can i fetch the last inserted contact number from the android phone book. I already done with the fetching all the unique contact numbers and its count from the database and i have also made a ContentObserver for listening the updates related to phone book.but now i want last inserted contact number. I am putting my whole code over here.
This is my code to fetch all the contacts :-
Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
while (phones.moveToNext()) {
phoneNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
listNumbers.add(phoneNumber);
}
ArrayList<String> unique = removeDuplicates(listNumbers);
for (String element : unique) {
System.out.println(element);
}
sb = new StringBuffer();
for (String item : unique) {
if (sb.length() > 0) {
sb.append(',');
}
sb.append(item);
}
numbersString = sb.toString();
// Log.e("BUFFER", numbersString.toString());
phones.close();
Log.e("SIZE", unique.size() + "");
And Now this is the code for contentobserver :-
private ContentObserver mObserver = new ContentObserver(new Handler()) {
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
final int currentCount = getContactCount();
if (currentCount < mContactCount) {
// DELETE HAPPEN.
Log.e("STATUS===>", "Deletion");
} else if (currentCount == mContactCount) {
// UPDATE HAPPEN.
} else {
// INSERT HAPPEN.
Log.e("STATUS===>", "Insertion");
}
mContactCount = currentCount;
}
};
In onchange INSERT Condition i want that newly added contact number.

How load all the contacts with minimum time in Android

In my project getting contacts is taking a long time to load.
What are ways to reduce the time of getting contacts
Assume there are 1000 contacts in my phone.
Right now it is taking more than 2 minutes to load all the contacts
How can I reduce the time to load contacts ?
Any Thoughts?
I referred to the the following link when programming the initial method.
http://www.coderzheaven.com/2011/06/13/get-all-details-from-contacts-in-android/
BETTER SOLUTION HERE.....
private static final String[] PROJECTION = new String[] {
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
};
.
.
.
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PROJECTION, null, null, null);
if (cursor != null) {
try {
final int nameIndex = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
final int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
String name, number;
while (cursor.moveToNext()) {
name = cursor.getString(nameIndex);
number = cursor.getString(numberIndex);
}
} finally {
cursor.close();
}
}
CHEERS...:)
Total time will depend upon what fields you are trying to access from the Contacts table.
Accessing less field means less looping , less processing and hence faster results.
Also to speed up your contacts fetch operation you can use the ContentProvideClient instead of calling query on ContentResolver every time. This will make you query the specific table rather than querying first for the required ContentProvider and then to table.
Create an instance of ContentProviderClient
ContentResolver cResolver=context.getContextResolver();
ContentProviderClient mCProviderClient = cResolver.acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI);
Then reuse this mCProviderClient to get Contacts(data from any ContentProvider) data on your call.
For example in following method, I am accessing only one field.
private ArrayList<String> fetchContactsCProviderClient()
{
ArrayList<String> mContactList = null;
try
{
Cursor mCursor = mCProviderClient.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if (mCursor != null && mCursor.getCount() > 0)
{
mContactList = new ArrayList<String>();
mCursor.moveToFirst();
while (!mCursor.isLast())
{
String displayName = mCursor.getString(mCursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
mContactList.add(displayName);
mCursor.moveToNext();
}
if (mCursor.isLast())
{
String displayName = mCursor.getString(mCursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
mContactList.add(displayName);
}
}
mCursor.close();
}
catch (RemoteException e)
{
e.printStackTrace();
mContactList = null;
}
catch (Exception e)
{
e.printStackTrace();
mContactList = null;
}
return mContactList;
}
Load Contact faster like other apps doing.
I have tested this code with multiple contacts its working fine and faster like other apps within 500 ms (within half second or less) I am able to load 1000+ contacts.
Total time will depend upon what fields you are trying to access from the Contacts table.
Mange your query according to your requirement do not access unwanted fields. Accessing less field means less looping , less processing and hence faster results.
Accessing right table in contact it also help to reduce contact loading time.
Query Optimization to load contact more faster use projection
String[] projection = {
ContactsContract.Data.MIMETYPE,
ContactsContract.Data.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.PHOTO_URI,
ContactsContract.Contacts.STARRED,
ContactsContract.RawContacts.ACCOUNT_TYPE,
ContactsContract.CommonDataKinds.Contactables.DATA,
ContactsContract.CommonDataKinds.Contactables.TYPE
};
Selection and selection argument
String selection = ContactsContract.Data.MIMETYPE + " in (?, ?)" + " AND " /*+ ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" + 1 + "' AND "*/ +
ContactsContract.Data.HAS_PHONE_NUMBER + " = '" + 1 + "'";
String[] selectionArgs = {
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
};
To order contacts alphabetically use following code
try {
Collections.sort(listview_address, new Comparator<ContactBook>() {
#Override
public int compare(ContactBook lhs, ContactBook rhs) {
return lhs.name.toUpperCase().compareTo(rhs.name.toUpperCase());
}
});
} catch (Exception e) {
e.printStackTrace();
}
Following is complete source code
public void initeContacts() {
List<ContactBook> listview_address = new LinkedList<ContactBook>();
SparseArray<ContactBook> addressbook_array = null;
{
addressbook_array = new SparseArray<ContactBook>();
long start = System.currentTimeMillis();
String[] projection = {
ContactsContract.Data.MIMETYPE,
ContactsContract.Data.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.PHOTO_URI,
ContactsContract.Contacts.STARRED,
ContactsContract.RawContacts.ACCOUNT_TYPE,
ContactsContract.CommonDataKinds.Contactables.DATA,
ContactsContract.CommonDataKinds.Contactables.TYPE
};
String selection = ContactsContract.Data.MIMETYPE + " in (?, ?)" + " AND " /*+ ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '" + 1 + "' AND "*/ +
ContactsContract.Data.HAS_PHONE_NUMBER + " = '" + 1 + "'";
String[] selectionArgs = {
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
};
String sortOrder = ContactsContract.Contacts.SORT_KEY_ALTERNATIVE;
Uri uri = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
uri = ContactsContract.CommonDataKinds.Contactables.CONTENT_URI;
} else {
uri = ContactsContract.Data.CONTENT_URI;
}
// we could also use Uri uri = ContactsContract.Data.CONTENT_URI;
// we could also use Uri uri = ContactsContract.Contact.CONTENT_URI;
Cursor cursor = getActivity().getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
final int mimeTypeIdx = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE);
final int idIdx = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID);
final int nameIdx = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
final int dataIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.DATA);
final int photo = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.PHOTO_URI);
final int typeIdx = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.TYPE);
final int account_type = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_TYPE);
while (cursor.moveToNext()) {
int contact_id = cursor.getInt(idIdx);
String photo_uri = cursor.getString(photo);
String contact_name = cursor.getString(nameIdx);
String contact_acc_type = cursor.getString(account_type);
int contact_type = cursor.getInt(typeIdx);
String contact_data = cursor.getString(dataIdx);
ContactBook contactBook = addressbook_array.get(contact_id);
/* if (contactBook == null) {
//list contact add to avoid duplication
//load All contacts fro device
//to add contacts number with name add one extra veriable in ContactBook as number and pass contact_data this give number to you (contact_data is PHONE NUMBER)
contactBook = new ContactBook(contact_id, contact_name, getResources(), photo_uri, contact_acc_type, "phone number");
addressbook_array.put(contact_id, contactBook);
listview_address.add(contactBook);
}*/
String Contact_mimeType = cursor.getString(mimeTypeIdx);
//here am checking Contact_mimeType to get mobile number asociated with perticular contact and email adderess asociated
if (Contact_mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
if (contactBook != null) {
contactBook.addEmail(contact_type, contact_data);
}
} else {
if (contactBook == null) {
//list contact add to avoid duplication
//load All contacts fro device
//to add contacts number with name add one extra veriable in ContactBook as number and pass contact_data this give number to you (contact_data is PHONE NUMBER)
contactBook = new ContactBook(contact_id, contact_name, getResources(), photo_uri, contact_acc_type, "phone number");
addressbook_array.put(contact_id, contactBook);
listview_address.add(contactBook);
}
// contactBook.addPhone(contact_type, contact_data);
}
}
cursor.close();
try {
Collections.sort(listview_address, new Comparator<ContactBook>() {
#Override
public int compare(ContactBook lhs, ContactBook rhs) {
return lhs.name.toUpperCase().compareTo(rhs.name.toUpperCase());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
You can use following code in above code that I have commented .It club the the single contact with its multiple number.To get all number associated with single contact use array in Object class.
if (contactBook == null) {
//irst contact add to avoid duplication
//load All contacts fro device
contactBook = new ContactBook(contact_id, contact_name, getResources(), photo_uri, contact_acc_type, "");
addressbook_array.put(contact_id, contactBook);
listview_address.add(contactBook);
}
String Contact_mimeType = cursor.getString(mimeTypeIdx);
//here am checking Contact_mimeType to get mobile number asociated with perticular contact and email adderess asociated
if (Contact_mimeType.equals(ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
contactBook.addEmail(contact_type, contact_data);
} else {
contactBook.addPhone(contact_type, contact_data);
}
Object class
public class ContactBook {
public int id;
public Resources res;
public String name;
public String photo;
public String contact_acc_type;
public SparseArray<String> emails;
public SparseArray<String> phones;
/* public LongSparseArray<String> emails;
public LongSparseArray<String> phones;*/
public String header = "";
public ContactBook(int id, String name, Resources res, String photo, String contact_acc_type, String header) {
this.id = id;
this.name = name;
this.res = res;
this.photo = photo;
this.contact_acc_type = contact_acc_type;
this.header = header;
}
#Override
public String toString() {
return toString(false);
}
public String toString(boolean rich) {
//testing method to check ddata
SpannableStringBuilder builder = new SpannableStringBuilder();
if (rich) {
builder.append("id: ").append(Long.toString(id))
.append(", name: ").append("\u001b[1m").append(name).append("\u001b[0m");
} else {
builder.append(name);
}
if (phones != null) {
builder.append("\n\tphones: ");
for (int i = 0; i < phones.size(); i++) {
int type = (int) phones.keyAt(i);
builder.append(ContactsContract.CommonDataKinds.Phone.getTypeLabel(res, type, ""))
.append(": ")
.append(phones.valueAt(i));
if (i + 1 < phones.size()) {
builder.append(", ");
}
}
}
if (emails != null) {
builder.append("\n\temails: ");
for (int i = 0; i < emails.size(); i++) {
int type = (int) emails.keyAt(i);
builder.append(ContactsContract.CommonDataKinds.Email.getTypeLabel(res, type, ""))
.append(": ")
.append(emails.valueAt(i));
if (i + 1 < emails.size()) {
builder.append(", ");
}
}
}
return builder.toString();
}
public void addEmail(int type, String address) {
//this is the array in object class where i am storing contact all emails of perticular contact (single)
if (emails == null) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
emails = new SparseArray<String>();
emails.put(type, address);
/*} else {
//add emails to array below Jelly bean //use single array list
}*/
}
}
public void addPhone(int type, String number) {
//this is the array in object class where i am storing contact numbers of perticular contact
if (phones == null) {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
phones = new SparseArray<String>();
phones.put(type, number);
/* } else {
//add emails to array below Jelly bean //use single array list
}*/
}
}}
For loading the contacts with mininum time the optimum solution is to use the concept of projection and selection argument while querying the cursor for contacts.
this can be done in following way
void getAllContacts() {
long startnow;
long endnow;
startnow = android.os.SystemClock.uptimeMillis();
ArrayList arrContacts = new ArrayList();
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER;
Cursor cursor = ctx.getContentResolver().query(uri, new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.Contacts._ID}, selection, null, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC");
cursor.moveToFirst();
while (cursor.isAfterLast() == false) {
String contactNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
String contactName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
int phoneContactID = cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone._ID));
int contactID = cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts._ID));
Log.d("con ", "name " + contactName + " " + " PhoeContactID " + phoneContactID + " ContactID " + contactID)
cursor.moveToNext();
}
cursor.close();
cursor = null;
endnow = android.os.SystemClock.uptimeMillis();
Log.d("END", "TimeForContacts " + (endnow - startnow) + " ms");
}
With above method it took 400ms(less than second) to load contacts where as in normall way it was taking 10-12 sec.
For details imformation this post might help as i took help from it
http://www.blazin.in/2016/02/loading-contacts-fast-from-android.html
If your time increases with your data, then you are probably running a new query to fetch phones/emails for every contact. If you query for the phone/email field using ContactsContract.CommonDataKinds.Phone.NUMBER, then you will just retrieve 1 phone per contact.
The solution is to project the fields and join them by contact id.
Here is my solution in Kotlin (extracting id, name, all phones and emails):
val projection = arrayOf(
ContactsContract.Data.MIMETYPE,
ContactsContract.Data.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Contactables.DATA
)
val selection = "${ContactsContract.Data.MIMETYPE} in (?, ?)"
val selectionArgs = arrayOf(
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE,
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
val contacts = applicationContext
.contentResolver
.query(ContactsContract.Data.CONTENT_URI, projection, selection, selectionArgs, null)
.run {
if (this == null) {
throw IllegalStateException("Cursor null")
}
val contactsById = mutableMapOf<String, LocalContact>()
val mimeTypeField = getColumnIndex(ContactsContract.Data.MIMETYPE)
val idField = getColumnIndex(ContactsContract.Data.CONTACT_ID)
val nameField = getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
val dataField = getColumnIndex(ContactsContract.CommonDataKinds.Contactables.DATA)
while (moveToNext()) {
val mimeType = getString(mimeTypeField)
val id = getString(idField)
var contact = contactsById[id]
if (contact == null) {
val name = getString(nameField)
contact = LocalContact(id = id, fullName = name, phoneNumbers = listOf(), emailAddresses = listOf())
}
val data = getString(dataField)
when(getString(mimeTypeField)) {
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ->
contact = contact.copy(emailAddresses = contact.emailAddresses + data)
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ->
contact = contact.copy(phoneNumbers = contact.phoneNumbers + data)
}
contactsById[id] = contact
}
close()
contactsById.values.toList()
}
And for reference, my LocalContact model:
data class LocalContact(
val id: String,
val fullName: String?,
val phoneNumbers: List<String>,
val emailAddresses: List<String>
)
I think this is a better solution:
public ContentValues getAllContacts() {
ContentValues contacts = new ContentValues();
ContentResolver cr = getContentResolver();
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if (cur != null && cur.getCount() > 0) {
while (cur.moveToNext()) {
String id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
String name = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
if (cur.getInt(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", new String[]{id}, null);
if (pCur != null) {
while (pCur.moveToNext()) {
String phoneNo = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contacts.put(phoneNo, name);
}
pCur.close();
}
}
}
cur.close();
}
return contacts;
}
for use it you need to call this lines once:
ContentValues contacts = new ContentValues();
contacts = getAllContacts();
and when you want to get contact name by number, just use:
String number = "12345";
String name = (String) G.contacts.get(number);
this algorithm is a bit faster...

Getting the next record into view from database

I have two buttons inside of my application, one for next and one for prev. I want the next button to get the next record inside of my database and display it inside of my view, and the prev button to get the previous record and display it inside of my view. How would I call the next or previous record? I have looked for tutorials and stuff but didn't find any. I anyone has a tutorial please share with me. Thanks for any help. I wish I had some code to provide but I really don't know where to start.
I use an int to pull the record from the dbase.
From my ContactView class
static long record = 1;
public void getData() {
DBase db = new DBase(this);
db.open();
lastRecord = db.lRec();
firstRecord = db.fRec();
rRec = db.getRec(record);
db.close();
}
then my query is from my Dbase class
public String[] getRec(long record) {
record = ContactView.record;
String[] columns = new String[] { KEY_ROWID, KEY_ONE, KEY_TWO,
KEY_THREE, KEY_FOUR, KEY_FIVE, KEY_SIX };
Cursor c = ourDatabase.query(DATABASE_TABLE, columns, KEY_ROWID + "="
+ record, null, null, null, null);
if (c != null && c.moveToFirst()) {
String rRec = c.getString(0);
String rOne = c.getString(1);
String rTwo = c.getString(2);
String rThree = c.getString(3);
String rFour = c.getString(4);
String rFive = c.getString(5);
String rSix = c.getString(6);
String[] rData = { rRec, rOne, rTwo, rThree, rFour,
rFive, rSix };
return rData;
}
return null;
}
and the next few are from my ContactView class
my buttons
#Override
public void onClick(View arg0) {
switch (arg0.getId()) {
case R.id.bSQLvPrev:
recordMinus();
display();
break;
case R.id.bSQLvNext:
recordPlus();
display();
break;
}
}
and the methods they call
public void display() {
etSQLvRec.setText(rRec[0]);
etSQLvOne.setText(rRec[1]);
etSQLvTwo.setText(rRec[2]);
etSQLvThree.setText(rRec[3]);
etSQLvFour.setText(rRec[4]);
etSQLvFive.setText(rRec[5]);
etSQLvSix.setText(rRec[6]);
}
public void recordPlus() {
record++;
}
public void recordMinus() {
record--;
}
That will get the record from the database based on the "record" variable, and the buttons increment it, or decrement it, it also skips any "empty" records.
EDIT OK, I had changed some stuff around since I lasted used my db, so use the next recordPlus() and recordMinus() code instead
public void recordPlus() {
if (record < lastRecord) {
record++;
} else {
record = firstRecord;
}
getData();
do {
if (record < lastRecord) {
record++;
} else {
record = firstRecord;
}
getData();
} while (rRec == null);
}
public void recordMinus() {
if (record == 1) {
record = lastRecord;
} else {
record--;
}
getData();
do {
if (record == 1) {
record = lastRecord;
} else {
record--;
}
getData();
} while (rRec == null);
}
And you'll need my fRec() and lRec() which find the first and last records in the DB
public long fRec() {
Cursor c = ourDatabase.query(DATABASE_TABLE, new String[] { "min(" +
KEY_ROWID
+ ")" }, null, null, null, null, null);
c.moveToFirst();
long rowID = c.getInt(0);
return rowID;
}
}
public long lRec() {
long lastRec = 0;
String query = "SELECT ROWID from Table order by ROWID DESC limit 1";
Cursor c = ourDatabase.rawQuery(query, null);
if (c != null && c.moveToFirst()) {
lastRec = c.getLong(0);
}
return lastRec;
}

Categories

Resources