I am using an Arraylist to fetch all the available contacts in my application. This is not efficient because the Arraylist takes a long time to fetch and populate the Listview as there are almost 600+ contacts.
I'm seeking an alternative approach that would have better performance.
Although I searched for other relevant questions but I was't able to find the convenient one.
Here is my java code:
private List<String> getContactList() {
List<String> stringList=new ArrayList<>();
ContentResolver cr = context.getContentResolver();
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI,
null, null, null, null);
if ((cur != null ? cur.getCount() : 0) > 0) {
while (cur != null && 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
);
while (pCur.moveToNext()) {
String phoneNo = pCur.getString(pCur.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.v("Data : ",""+id+" "+name+" "+phoneNo);
stringList.add(id);
stringList.add(name);
stringList.add(phoneNo);
}
pCur.close();
}
}
}
if(cur!=null){
cur.close();
}
return stringList;
}
Your query is inefficient, you're currently doing a query per contact which is very slow, you can get it all with one big query (which is pretty fast):
String[] projection = new String[] { Phone.CONTACT_ID, Phone.DISPLAY_NAME, Phone.NUMBER };
Cursor c = cr.query(Phone.CONTENT_URI, projection, null, null, null);
while (c.moveToNext()) {
long contactId = c.getLong(0);
String name = c.getString(1);
String phone = c.getString(2);
Log.i("Phones", "got contact phone: " + contactId + " - " + name + " - " + phone);
}
c.close();
You could consider using the Paging library: https://developer.android.com/topic/libraries/architecture/paging/
It was designed with the idea that a list only displays a certain number of items, so there's really no need to load way way way more than it can potentially show. For example, a ListView might only show 10 contacts, so there's no need to fetch 600 contacts.
Instead, the Paging library will fetch a smaller amount as the user scrolls, thus erasing the loading time of 600 contacts, the memory for 600 contacts, and etc... thus making it more efficient.
If you're worried of speed, I would try to use a Set, although with 600+ contacts in the ArrayList that shouldn't be a problem. It becomes a problem when the data set is in the millions and more. I would try to looking at any other inefficiencies in your code.
In terms of a Set, the two most common Java data structures are HashSet and TreeSet. TreeSet if you want to the set to be ordered. HashSet is a little bit faster, but you lose out on the ordering. Both of which has O(1) access time.
Related
When downloading images from browser and listing them into the app, date taken column is always null only on android 10+
ArrayList<String> localImages = new ArrayList<>();
Uri uri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon().build();
final String[] projection = {
MediaStore.MediaColumns.DATA,
MediaStore.Images.Media.BUCKET_DISPLAY_NAME,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.MediaColumns.DATE_ADDED
};
final String orderBy = MediaStore.Images.Media.DATE_TAKEN;
String searchQuery = null;//dateQuery == null ? null : MediaStore.Images.Media.DATE_TAKEN + ">" + (dateQuery.getTime());
Cursor cursor = this.getContentResolver().query(uri, projection, searchQuery, null, orderBy + " DESC");
int columnIndexData = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
int columnDateTaken = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN);
while (cursor.moveToNext()) {
String imagePath = cursor.getString(columnIndexData);
String dateTaken = cursor.getString(columnDateTaken); // THIS IS ALWAYS NULL
localImages.add(imagePath);
}
"Media Store insert DATE_TAKEN is always null"
DATE_TAKEN is documented to be "The time the media item was taken." and to deliver on that API description, we populate it based on the "DateTimeOriginal" Exif metadata field, or the . If the file being scanned doesn't have this metadata, we can't accurately determine when the file was captured, so DATE_TAKEN is set to NULL to avoid misleading data.
So, some devices don't have DATE_TAKEN field.If you use DATE_TAKEN, you will check value is null or not. And when not, you should fall back to use DATE_ADDED or DATE_MODIFIED, etc.
I referred to this Japanese article: https://zenn.dev/tsutou/articles/59fbb5e71b2988
What I'm trying to achieve is to add shortcut to my app in android book contact details, similar to what whatsapp is doing.
I've been following this tutotial: http://blogs.quovantis.com/syncing-contacts-with-an-android-application-2/ and it works well but the author doesn't show how to pass data from contact details to ViewingActivity: https://github.com/ajkh35/ContactsDemo/blob/master/app/src/main/java/com/example/ajay/contacts_4/ViewingActivity.java
There was some comments below the article but no specific answer from the author, can't find anything useful in
Uri data = getIntent().getData(); //content://com.android.contacts/data/1169
List<String> params = data.getPathSegments();
String first = params.get(0);
String second = params.get(1);
there is some number passed in second param but it's not CONTACT_ID or RAW_CONTACT_ID. Any help?
Ok, so it seems the Uri you're getting from the Contacts app is a Data uri.
Data rows contain info about a specific data-item (like a phone number or an email) of a specific RawContact, so a single Data row "belongs" to a single RawContact which "belongs" to a single Contact.
Luckily, the ContactsContract API allows for implicit joins when querying the Data table, so you can do something like this:
Uri dataUri = getIntent().getData(); //content://com.android.contacts/data/1169
String[] projection = new String[]{
Data.CONTACT_ID,
Data.RAW_CONTACT_ID,
Data.DISPLAY_NAME,
Data.MIMETYPE,
Data.DATA1};
Cursor cur = getContentResolver().query(dataUri, projection, null, null, null);
cur.moveToFirst(); // there should always be exactly one result, since we have a specific data uri here
Log.i("Contact Info", "Got info: id=" + cur.getLong(0) + ", raw-id=" + cur.getLong(1) + ", " + cur.getString(2) + ", " + cur.getString(3) + ", " + cur.getString(4));
cur.close();
I know this is a very late response but checkout the following code.
class MessageActivity : AppCompatActivity() {
private val TAG: String = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_message)
if(intent != null && intent.data != null) {
Log.e(TAG, intent.data.toString())
var contactName = ""
val cursor = contentResolver.query(intent.data!!,
arrayOf(ContactsContract.Data.DATA1,ContactsContract.Data.DATA2,ContactsContract.Data.DATA3),
null,null,null)
if(cursor != null && cursor.moveToFirst()) {
do{
Log.e(TAG, cursor.getString(cursor
.getColumnIndexOrThrow(ContactsContract.Data.DATA1)))
contactName = cursor.getString(cursor
.getColumnIndexOrThrow(ContactsContract.Data.DATA2))
Log.e(TAG, contactName)
Log.e(TAG, cursor.getString(cursor
.getColumnIndexOrThrow(ContactsContract.Data.DATA3)))
}while (cursor.moveToNext())
cursor.close()
}
messaging_text.text = getString(R.string.messaging) + " $contactName"
}
}}
So when you register a contact you set some Data1, Data2 and Data3 values. Data3 is what gets displayed in the contacts. You can set Data1 and Data2 some value you like and then retrieve it like in the code I mentioned above.
You can also checkout my blog here. Look for the "Sync Service" section, towards the end you will find the MessageActivity.
Thanks & regards
I am trying to get the phone contacts and store it in a Hashmap. I want to save that locally and use it anywhere in the project.
Following is my code to get phone contacts:
public HashMap getPhoneContacts() {
ArrayList contactList=null;
ContentResolver cr = getContext().getContentResolver(); //Activity/Application android.content.Context
Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null
, null, null, null);
if(cursor.moveToFirst())
{
contactList = new ArrayList<String>();
do
{
String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
String contactName=cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
if(Integer.parseInt(cursor.getString(cursor.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);
while (pCur.moveToNext())
{
String contactNumber = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
//String contactId = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
String noramliseNum;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
noramliseNum = PhoneNumberUtils.normalizeNumber(contactNumber);
}else{
noramliseNum=contactNumber.replaceAll("\\s","");
}
phoneContacts.put(noramliseNum,contactName);
break;
}
pCur.close();
}
} while (cursor.moveToNext()) ;
}
return phoneContacts;
}
Its already taking lot of time to fetch all the contacts. So I dont want to call this function again and again from other classes. Instead I need to call the function only once,and then store it in a Hashmap locally and use it whenever we want,so that it wont take time to fetch the details again.
Please help.
You could put it into SharedPreferences if you don't want to re-download every time you start the app. Then, once the data is retreived, you could have a Singleton class to hold your Hashmap.
If you have many contacts, you should consider using a database.
Take a look at https://developer.android.com/guide/topics/data/data-storage.html
This would be more efficient than keeping everything in memory.
I need to implement an algorithm that access to the database to check the last elemnt in order to calculate the new one. Of course, the first time it would be impossible because the database is empty and I get
IndexOutOfBoundsException) index 0 requested with a size of 0
To avoid it, I tried to check if cursor.getCount==0, and then I introduce a default first element. The problem is that, even when I have stored it in the database, cursor.getCount keeps being 0. Here is the code of my method:
public Measures getLastMeasure(String date) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_MEASURES, new String[] { KEY_ID,
KEY_DATE, KEY_TIME_HOUR, KEY_TIME_MINUTE, KEY_BE_INTAKE,
KEY_GLUCOSE, KEY_BOLUS, KEY_BASAL }, KEY_DATE + "=?",
new String[] { date }, null, null, KEY_TIME_HOUR + " DESC, "
+ KEY_TIME_MINUTE + " DESC", "1");
if (cursor.getCount() == 0) {
Measures m = new Measures("nodate", 0, 0, 0, 0, 0, 0);
return m;
} else {
if (cursor != null)
cursor.moveToFirst();
Measures m = new Measures(Long.parseLong(cursor.getString(0)), // id
cursor.getString(1), // date
Integer.parseInt(cursor.getString(2)), // timeHour
Integer.parseInt(cursor.getString(3)), // timeMinute
Double.parseDouble(cursor.getString(4)), // BE intake
Double.parseDouble(cursor.getString(5)), // glucose
Double.parseDouble(cursor.getString(6)), // bolus
Double.parseDouble(cursor.getString(7)) // basal
);
// return m
return m;
}
}
I hope you can help me. Thanks in advance to everyone.
I could be missing something, but that code looks correct to me. Are you certain that the data you're looking for is actually in the database and that the argument you're passing to getLastMeasure() is correct?
Looks like your query returns no data matching your search. If you only want latest data by date, I suggest order by KEY_DATE desc and limit 1, or max(KEY_DATE) instead of KEY_DATE + "=?"
Side note: no need to check if cursor is null (SQLiteDatabase#query never returns a null Cursor), and anyway your check is useless because you're accessing cursor even if it's null. Also you're not closing the Cursor.
I wrote the following function in order to retrieve one single phone number that belongs to the contact with id "contactID".
The function which is to retrieve the phone number:
private String getContactPhone(String contactID) {
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String[] projection = null;
String where = ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?";
String[] selectionArgs = new String[] { contactID };
String sortOrder = null;
Cursor result = managedQuery(uri, projection, where, selectionArgs, sortOrder);
if (result.moveToFirst()) {
String phone = result.getString(result.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
if (phone == null) {
result.close();
return null;
}
result.close();
return phone;
}
result.close();
return null;
}
How this function is called:
ArrayList<Contact> resultContacts = new ArrayList<Contact>();
Cursor result = null;
Uri uri = ContactsContract.Data.CONTENT_URI;
String[] projection = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Event.CONTACT_ID,
ContactsContract.CommonDataKinds.Event.START_DATE,
};
String where = ContactsContract.Data.MIMETYPE+" = ? AND "+ContactsContract.CommonDataKinds.Event.TYPE+" = "+ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY;
String[] selectionArgs = new String[] {ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE};
String sortOrder = null;
result = managedQuery(uri, projection, where, selectionArgs, sortOrder);
while (result.moveToNext()) {
Long id = result.getLong(result.getColumnIndex(ContactsContract.Contacts._ID));
String phone = getContactPhone(String.valueOf(id));
...
}
...
Unfortunately, it doesn't work. I get null if I call this function with the value that I got from "ContactsContract.Contacts._ID". Why is this so? What is wrong?
Edit: I used to map Contacts._ID to CommonDataKinds.Phone.CONTACT_ID - which didn't work. But now I map Contacts.DISPLAY_NAME to CommonDataKinds.Phone.DISPLAY_NAME and it works suddenly - strange, isn't it? But I would rather like to map the IDs instead of the display names. So the question is still topical. Could this be due to different IDs in those tables? Isn't this why there are lookup IDs?
To get the contact id in the first part, you should use:
ContactsContract.Data.CONTACT_ID
instead of:
ContactsContract.Contacts._ID
So the projection should be:
String[] projection = new String[] {
ContactsContract.Data.CONTACT_ID,
ContactsContract.CommonDataKinds.Event.CONTACT_ID,
ContactsContract.CommonDataKinds.Event.START_DATE,
};
And then of course get the correct row:
Long id = result.getLong(result.getColumnIndex(ContactsContract.Data.CONTACT_ID));
You are getting null because you have set your projection to null. The projection is basically the list of columns that you want returned e.g.
String[] projection = {ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.HAS_PHONE_NUMBER};
Usually, when you find the contact, they may have a list of phone numbers, so you have to use another cursor to iterate through the phone numbers, e.g.
Cursor phones = mContext.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId, null, null);
while (phones.moveToNext())
{
phoneNumber = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DATA));
}
Hope this helps.
Your code for getContactPhone() works fine on my end. I tested by launching a contact picker, selecting a contact, then using the ID that was returned and passed that into your method.
So I suspect you are indeed passing in an invalid ID. Can you post the full stack trace for the null pointer exception?
Yes, lookup keys are available because _IDs are not guaranteed to stay the same since syncs and contact aggregation changes them.