I want to fetch a list of local contacts with an anniversary date set.
I'm doing the following:
object WithAnniversary {
const val INDEX_CONTACT_ID = 0
const val INDEX_CONTACT_NAME = 1
const val INDEX_CONTACT_ANNIVERSARY = 2
const val INDEX_CONTACT_PHOTO = 3
val PROJECTION = arrayOf(
CommonDataKinds.Event.CONTACT_ID,
ContactsContract.Contacts.DISPLAY_NAME,
CommonDataKinds.Event.START_DATE,
CommonDataKinds.Phone.PHOTO_URI
)
const val WHERE = "${ContactsContract.Data.MIMETYPE} = ? AND " +
"${CommonDataKinds.Event.TYPE} = " +
"${CommonDataKinds.Event.TYPE_ANNIVERSARY}"
val SELECTION = arrayOf(CommonDataKinds.Event.CONTENT_ITEM_TYPE)
val SORT_ORDER: String? = null
}
#Throws(Exception::class)
fun obtainContactsWithAnniversaries(): List<Contact> {
val list = mutableListOf<Contact>()
val cursor = context.contentResolver.query(
ContactsContract.Data.CONTENT_URI,
WithAnniversary.PROJECTION,
WithAnniversary.WHERE,
WithAnniversary.SELECTION,
WithAnniversary.SORT_ORDER
)
if (cursor != null) {
while (cursor.moveToNext()) {
val id = cursor.getLong(WithAnniversary.INDEX_CONTACT_ID)
val name = cursor.getString(WithAnniversary.INDEX_CONTACT_NAME)
val date = cursor.getString(WithAnniversary.INDEX_CONTACT_ANNIVERSARY)
val avatarUri = cursor.getString(WithAnniversary.INDEX_CONTACT_PHOTO)
try {
val contact = contactFactory.create(id.toString(), name, null, date, avatarUri)
list.add(contact)
} catch (e: Exception) {
Log.d(TAG, "Could not parse contact with name: $name")
}
}
cursor.close()
return list.sorted()
} else {
throw Exception("Unable to retrieve contacts, returned cursor is null")
}
}
I use the exact same process for retrieving contacts with birthday dates, but using TYPE_BIRTHDAY instead of TYPE_ANNIVERSARY, but for some reason this doesn't work for anniversaries.
I have checked my local contact list and I have some contacts with birthdays and anniversaries. I can retrieve a list with contacts with birthdays but the list of contacts with anniversaries is empty.
Any help will be appreciated.
I assume the issue is with the conversion of the cursor row to your custom Contact class.
When I replace that part with just a log your code works for me:
...
while (cursor.moveToNext()) {
val id = cursor.getLong(WithAnniversary.INDEX_CONTACT_ID)
val name = cursor.getString(WithAnniversary.INDEX_CONTACT_NAME)
val date = cursor.getString(WithAnniversary.INDEX_CONTACT_ANNIVERSARY)
val avatarUri = cursor.getString(WithAnniversary.INDEX_CONTACT_PHOTO)
Log.d("TEMP", "contact $id $name $date $avatarUri")
}
...
Log:
D/TEMP: contact 98014 Test1 1979-10-06 content://com.android.contacts/contacts/98014/photo
contact 4603 test 1990-07-22 content://com.android.contacts/contacts/4603/photo
contact 98341 Voice Mail 2013-11-06 null
Related
So... Yesterday my code was working just fine, but today, I don't know what happend it stoped working.
The cursor is null and returns both "". I don't know what to do.
Is the cursor even working?
#SuppressLint("Range", "Recycle")
#Composable
fun ContactPickerTwinTurbo(
done: (String, String) -> Unit
) {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickContact(),
onResult = {
val contentResolver: ContentResolver = context.contentResolver
var name = ""
var number = ""
val cursor: Cursor? = contentResolver.query(it!!, null, null, null, null)
if (cursor != null) {
if (cursor.moveToFirst()) {
name =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
Log.d("Name", name)
val id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
val phones: Cursor? = contentResolver.query(
Phone.CONTENT_URI, null,
Phone.CONTACT_ID + " = " + id, null, null
)
if (phones != null) {
while (phones.moveToNext()) {
number = phones.getString(phones.getColumnIndex(Phone.NUMBER))
Log.d("Number", number)
}
phones.close()
}
}
}
done(name, number)
}
)
Button(
onClick = {
launcher.launch()
},
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Text(text = "Pick Contact")
}
}
How does the cursor works?
Do I have to wait?
am I supposed to ask permission to access the contacts?
Apparently Yes you need to ask for permission in runtime to get access the contact. For References check this link
[check link][1]https://www.geeksforgeeks.org/contact-picker-in-android-using-jetpack-compose/
Replace: var name = "" -> var name = remember{ mutableStateOf("") }
var number = "" -> var number = remember{ mutableStateOf("") }
and while accessing, use name.value and number.value
I've got the following code to retrieve a contact list with Name + PhoneNumber:
#SuppressLint("Range")
fun getNamePhoneDetails(): ArrayList<List<String>>? {
val names = ArrayList<List<String>>()
val cr = contentResolver
val cur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
null, null, null)
if (cur!!.count > 0) {
while (cur.moveToNext()) {
val id = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NAME_RAW_CONTACT_ID))
val name = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
val number = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
names.add(listOf(id, name, number))
}
}
return names
}
The output is correct once no contact has two phone numbers. However once any contact has two phone numbers I get the contact twice. It has the same id and name, but a different phone number. How can I make it so that the returned list does not have a contact twice but all phone numbers inside a list?
Something like
[1, Name Name, [phonenumber1, phonenumber2]],[2, Name Name, [phonenumber1]]
Then I could just iterate through the phonenumberlist and have all numbers as valid strings.
You should rather create a POJO Object and than assign the data into it and return a list of that POJO Object and then you can make use of distinct extension on the list to get a filtered result they way you want it .
This is how you can achieve what you want :
Create a POJO Object :
data class Contact(
val id : String ,
val name : String,
val number : String)
And then when retrieveing the data you can do the following :
#SuppressLint("Range")
fun getNamePhoneDetails(): MutableList<Contact>? {
val names = MutableList<Contact>()
val cr = contentResolver
val cur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
null, null, null)
if (cur!!.count > 0) {
while (cur.moveToNext()) {
val id = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NAME_RAW_CONTACT_ID))
val name = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
val number = cur.getString(cur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
names.add(Contact(id , name , number))
}
}
return names
}
And then while retreiving the data you need to just filter the list in the following manner :
val list = getNamePhoneDetails()
list.distinctBy { it.number }.forEach { println(it) }
Working on a chatting app I want to get the list of users(B, C, D,..) whose contacts are saved in the user (A) mobile phone.
First I fetch user(A) contacts from the phone and store them in an ArrayList (phoneContactArrayList). Secondly, I fetch the user's phone numbers registered on my app and also store them in ArrayList (dbContactArrayList).
Now I want to compare both of these array lists and get the common contacts numbers out of them, which are those contacts(B, C, D,...) of the user(A) registered on my app and the user(A) can contact them via my app.
For this here is the method to fetch contacts from the User(A) mobile phones.
private fun getContactList() {
phoneContactArrayList = ArrayList()
val cr = contentResolver
val cur = cr.query(
ContactsContract.Contacts.CONTENT_URI,
null, null, null, null
)
if (cur?.count ?: 0 > 0) {
while (cur != null && cur.moveToNext()) {
val id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID))
val name = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
if (cur.getInt(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
val pCur = cr.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
arrayOf(id), null
)
while (pCur!!.moveToNext()) {
phoneContactArrayList?.clear()
val phoneNo = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
phoneContactArrayList!!.add(phoneNo)
Log.i("Users Ph.Contacts List=" , phoneContactArrayList.toString())
// all users contacts are shown successfully as I check in Logcat
}
pCur.close()
}
}
}
cur?.close()
}
Here is the method for fetching the users registered on my app via firebase authentication.
private fun getFirebaseContacts() {
dbContactArrayList = ArrayList()
FirebaseDatabase.getInstance().getReference("UserProfile")
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(contactList: DataSnapshot) {
try {
dbContactArrayList?.clear()
for (eachContactList in contactList.children) {
// Log.e("TAG", "onDataChange: " + eachContactList.value.toString())
var contactModel: SignUpEntity =
eachContactList.getValue(SignUpEntity::class.java)!!
val mData = contactModel.userPhone
if (mData != null) {
dbContactArrayList?.add(mData)
}
Toast.makeText(applicationContext,"Firebase Users List=${dbContactArrayList.toString()}",
Toast.LENGTH_SHORT
).show() // successfully toast the numbers registered on app
}
} catch (e: Exception) {
//Log.e("Exception",e.toString())
}
}
override fun onCancelled(p0: DatabaseError) {
TODO("Not yet implemented")
}
})
}
And here is the method in which both ArrayList are compared to get the common contacts, the resultArrayList is always empty.
private fun getMatchedContacts(dbContactArrayList: ArrayList<String>?, phoneContactArrayList: ArrayList<String>?) { // here on debugging I get to know both arrayLists are of size 0.
resultArrayList = ArrayList()
for (s in phoneContactArrayList!!) {
resultArrayList?.clear()
if (dbContactArrayList!!.contains(s) && !resultArrayList!!.contains(s)) {
resultArrayList!!.add(s)
}
}
Log.e("Result Values", resultArrayList.toString())
}
It's because your resultArrayList is being cleared at each iteration of your for loop. Try to remove resultArrayList?.clear().
private fun getMatchedContacts(dbContactArrayList: ArrayList<String>?, phoneContactArrayList: ArrayList<String>?) { // here on debugging I get to know both arrayLists are of size 0.
resultArrayList = ArrayList()
for (s in phoneContactArrayList!!) {
if (dbContactArrayList!!.contains(s) && !resultArrayList!!.contains(s)) {
resultArrayList!!.add(s)
}
}
Log.e("Result Values", resultArrayList.toString())
}
If you only want to get the common elements of two lists, then in Kotlin it will be as simple as:
val l1 = listOf(1, 2, 3, 4, 5)
val l2 = listOf(1, 3, 5, 7, 9)
val common = l1.filter { i -> l2.contains(i) }
Log.d(TAG, common.toString())
The result will be:
[1, 3, 5]
I have written the following query to read contacts from a device.
private fun getContactPhoneNumbers(resolver: ContentResolver): Map<String, NameAndPhoneList> {
val startTime = System.nanoTime()
val map = hashMapOf<String, NameAndPhoneList>()
val projection = arrayOf(
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER,
ContactsContract.Contacts.PHOTO_THUMBNAIL_URI
)
val selection =
"${ContactsContract.Contacts.DISPLAY_NAME} NOT LIKE '' and ${ContactsContract.Contacts.DISPLAY_NAME} NOT NULL"
resolver.query(
ContactsContract.Contacts.CONTENT_URI,
projection,
selection,
null,
null,
null
)?.let { cursor ->
if (cursor.count > 0) {
while (cursor.moveToNext()) {
val id: String? =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID))
val name: String? =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME))
val photoUri =
cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI))
val phone =
if (cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
val pCur: Cursor = resolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
arrayOf(id),
null
)!!
val numbers = mutableListOf<String>()
while (pCur.moveToNext()) {
val phoneNo: String = pCur.getString(
pCur.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER
)
)
numbers.add(phoneNo)
}
pCur.close()
numbers
} else
null
// take contacts which either have email or phone numbers
if (id != null && name != null) {
map[id] = NameAndPhoneList(name, phone, photoUri?.let { Uri.parse(it) })
}
}
}
cursor.close()
}
val endTime = System.nanoTime() - startTime
Timber.i("$CONTACT_SYNC_PHONE_MAP_QUERY_TIME = $endTime")
return map
}
private data class NameAndPhoneList(
val name: String,
val phoneList: List<String>?,
val imageUri: Uri?
)
this is taking 112877872699 ns (~2 min) for a phonebook of length 6,300 contacts. Is this expected or can we optimize further?
for 6,300 contacts (assuming all have a phone) you're making 6,301 queries, which is why it's so slow...
Instead you can benefit from ContactsContract's "implicit join" feature, which allows you to get Contacts.CONTENT_URI fields when querying over the Data.CONTENT_URI table.
So, just query directly over the Data table, get all the phones including the CONTACT_ID, DISPLAY_NAME, etc. fields, and put it in some map contact-id => data.
Here's sample code in Java that can help - https://stackoverflow.com/a/44383937/819355
I am able to get user phone numbers from the contacts list, but i also need names with the numbers,
I know if i use custom adapter then i can get name and number both, but i want to use the default contact picker.
This is my code.
private fun launchMultiplePhonePicker() {
val phonebookIntent = Intent("intent.action.INTERACTION_TOPMENU")
phonebookIntent.putExtra("additional", "phone-multi")
phonebookIntent.putExtra("maxRecipientCount", 20)
phonebookIntent.putExtra("FromMMS", true)
startActivityForResult(phonebookIntent, 110)
}
This does work fine, but only returns phone numbers, and not contact names in onActivityResult.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val bundle = data?.extras
val result = bundle?.getString("result")
val contacts = bundle?.getStringArrayList("result")
}
Edit:
I found out that intent.action.INTERACTION_TOPMENU may not work in all devices, so i used the following approach, its giving me the names with number, but not allowing me to select multiple contacts.
val intent = Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
startActivityForResult(intent, 10101)
My simple solution to get contacts, may be it will help you
Data class to hold extracted values:
data class ContactModel(val phoneNumber: String, val displayName: String)
Get contacts and map to model
val result = arrayListOf<ContactModel>()
val cursor.context.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, ContactsContract.Contacts.SORT_KEY_PRIMARY + " ASC")
cursor?.let {
val nameIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
val phoneIndex = it.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
while (it.moveToNext()) {
val name = cursor.getString(nameIndex)
val phone = cursor.getString(phoneIndex)
var num = phone
.replace(" ", "")
.replace("-", "")
.replace("(", "")
.replace(")", "")
val contactModel = ContactModel(num, name)
//Prevents duplicated contacts on some devices
if (it.position != 0) {
if (contactModel != result[result.size - 1]) {
result.add(contactModel)
}
} else {
result.add(contactModel)
}
}
}
If you have a list of phone numbers and you need to get the display names, you can use ContactsContract.PhoneLookup, something like this:
private fun phoneToName(phone: String): String? {
val uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phone))
var cur = getContentResolver().query(uri, arrayOf(PhoneLookup.DISPLAY_NAME), null, null, null)
if (cur.moveToFirst()) {
return cur.getString(0)
}
cur.close()
return null
}
P.S. just note that "intent.action.INTERACTION_TOPMENU" is not an official Android API, and is probably not supported by all devices.
EDIT: there's no official way of using the phone-picker for multiple contacts, either you implement your own contact list and let the user choose multiple contacts within your app, or you can allow the user to pick contacts multiple times until they finish.