I wanted to share a couple of brief tips for developers who are working with contacts on iPhone and iPod touch, where the user has enabled syncing with services like MobileMe or Microsoft Exchange.
These are lessons I’ve learned when diagnosing and fixing a couple of bugs which my (most beloved) users of Favorites have reported. If you’re an iPhone developer who’s doing things with the Address Book API, you may want to take note.
Unique record-identifiers change after MobileMe sync
If you’re storing references to the user’s contacts, you’re probably doing so by keeping the record-IDs, on the assumption that these are unique (they are) and will not change for a given contact (wrong). If the user enables MobileMe sync for their contacts, there’s a very high chance that the record-IDs will change. This is presumably to enforce a globally-unique (across all synced devices) identifier in the cloud. It’s also pretty annoying.
One solution would be, when you’re storing a contact’s ID, also grab their full composite name (via ABRecordCopyCompositeName()) and store that too. When you’re looking up a contact later, if the ID-based look-up fails (ABAddressBookGetPersonWithRecordID()), fall back on a composite-name-based look-up instead (ABAddressBookCopyPeopleWithName()). You can also take note of more fields, and do more advanced matching if you feel it’s necessary. If you find a suitable contact, be sure to update your copy of the ID.
It would be nice if Apple fixed this somehow (such as divorcing the device-local unique identifiers from the global cloud-based identifiers), but for now you’ll have to do a little bit of work to ensure your contact-references behave as the user expects.
Labels can be NULL for Exchange contacts
If your user syncs with Microsoft Exchange, the labels (like “work” or “mobile” or whatever) for each phone-number and email address can and often will be NULL (and I mean really NULL, not just an empty string). This can be unexpected, because it’s not possible to create a contact whose numbers/email addresses have a NULL label using the Address Book (Mac) or Contacts (iPhone) UIs.
Just be sure to code defensively, and not assume that a label will always exist even though there’s a number or email address. It’s a very small additional check which you should probably be doing anyway, but I thought it was worth pointing out because it’s perhaps not something you’d otherwise expect.
If you have any other tips relating to dealing with contacts on iPhone from a developer’s perspective, feel free to post them below.
It’s funny, I was thinking about the ID problem a couple of weeks ago, even though I’m not working on anything relevant. It didn’t take me long to conclude that the DB should be tracking sets of IDs rather than a single value. Pity I didn’t work for Apple back when they made the address book, eh? :-)
Thanks for sharing! It’s comforting to know that other people are banging their heads against the same walls that you are :-)
In addition to the composite name, I also store the first and last names separately. The problem being that the RecordID’s are by default a simple 32 bit int sequence starting at 1. I haven’t been able to test this yet, but the ID’s are documented as only being unique -per device-. I expect that moving or syncing contacts from one device to another will result in overlapping ID ranges. In this case, getting a record by RecordID could well succeed but give you the wrong person. oops :-)
Also, if ABAddressBookCopyPeopleWithName fails when using the composite name (for example, because a person changed their jobs – new company name, new composite name *sigh*) you can fall back further to using just their actual name. (don’t ask me what do to when someone marries between syncs)
Quite a lot of work to enhance contact data with application data and keep them synchronized. (I’m really glad we can at least use background threads for this ;-)
Incrementing numbers suck. UUIDs for the reasonably high degree of win.
I have not had an opportunity to investigate this, but is the uniqueId (integer) different from the ABRecord: kABUIDProperty? Could you set the kABUIDProperty? Would it persist through a sync?
What about adding a hidden custom property? I know this is a little counter intuitive but this is almost storing most of your favorites information in the address book rather than your own database.
Oh you could also add a custom URL scheme and store everything in the person url property….
HI David,
Address Book on the iPhone is quite different to its cousin on Mac OS X. That property you mentioned is only present in the OS X version of the API, for example, and you’re also limited to the pre-defined properties on iPhone – you can’t add arbitrary new ones as you can on OS X.
Regarding storing relevant info in a standard field like the URL field, that would possibly work but would be messy when those URLs were synced back to other devices.
Also, the only programmatic ways to search for contacts via the iPhone API are by full (composite) name or by record-ID; you cannot search by arbitrary fields, as you can on OS X. Thus, locating a contact for a given favorite using any other field would involve iteration through all contacts, which would be very inefficient.
This is further complicated because you can create multiple address book entries with the same name. There is nothing stopping a person from having 2 identically named records that hold different information. i.e. you can have 2 contacts named Kate Bell with different Mobile numbers.
This is a bit of a disaster really. Nothing is 100%. Even presenting a user with a pick list if there are 2 results after doing a name search would leave them guessing which one they really meant.
Also, for those considering ‘background updates’ keep in mind that there are more users than you would anticipate that have many thousands of contacts. This is both memory intensive and time consuming to sync.
Matt – not sure how much you played around with this, but getting a person by Composite Name with the function ABAddressBookCopyPeopleWithName() doesn’t actually work in all cases. It only works for the simplest form of a name, but will not work if they use other fields such as suffix or middle name. In the default addressbook in xcode simulator, you can get Anna Haro, but you can’t get Daniel Higgins Jr. or Hank M. Zakroff. The Jr. and M. throw it off.
I’m quite amazed at the size of this hole. Its pretty much a killer for my app.
Unfortunately, Apple’s MobileMe sync doesn’t even work properly for Apple. Can we band together and ask Apple to fix this? I’ve used MobileMe and .Mac since they were first available, and constantly have to back up and refresh my contacts as they get mangled through syncing. It’s frustrating. I have thousands of contacts as I run my own Mac/iPhone software development firm…
To Apple’s credit, it is a difficult problem, complicated by the need to support third party sources like Exchange/Outlook. Every device has its own method of uniquely identifying records, which become meaningless when comparing/syncing with other sources. Non-Apple devices discard, ignore, or corrupt this metadata.
I personally think Apple with its deep pockets should fund an open standard for storing contacts that everyone can adopt. Even Microsoft. There’s no excuse for not solving this problem. If computers can sequence the human genome, model global weather patterns, and calculate PI to tons of decimal places… they ought to be able to handle my contacts better than an old-fashioned Rolodex. :-)
Matt, thanks for mentioning that Exchange-synced NULL label issue. That one bit me and finding your post helped pin down the problem.
Once the contact in iphone is synced with Microsoft Exchange, programming on contact may fail with no error notification. It means when I try to set an email to an existing contact with custom label. The code execute corrrectly(&errror parameter will be null after operation) but the email is not added to that contact actually.
Any idea about it?
Has there been any improvement on the unique ID issue? Browsing the files in the iPhone backup, the file (from the iPhone):
/Library/Preferences/com.apple.mobilephone.speeddial.plist
lists the favorites from the phone app. This file contains an array of dicts that include the simple ABIdentifier as well as additional keys for ABDatabaseUUID (a true UUID), Label, Name, Property, Value, and also a key-value pair named ABUid which appears to also be a simple integer (instead of a UUID). Any ideas where this ABUid value comes from or where it is stored for use as a key to find a unique contact?
Hello All,
I am developing an iPhone application, which requires to know the running device name and phone number. Is there a way to achieve this through address book. I am successfully able to read the device address book.
Does the address book has an device owners phone entry n it, and if it does then how can we differ it from other contacts?
Looking forward for the reply.
Thanks,
Pankaj
Hello everyone,
I haven’t tested this on iPhone/iPod Touch, but one possibility would be to store a custom UUID in an X-type property. This custom property will not be visible in the UI but should travel with the record and remain unchanged.
There used to be an issue in MobileMe (~2006) where custom properties were not being synced. I don’t know if this is still a problem though.
– Tito
One more tip is that in some real-world situations, even the most basic operation, ABRecordCopyCompositeName, can return null or be empty. I realized that with my app, Contacts Journal (http://appsto.re/contactsjournal) and it was causing a memory issue for some people .
Hi Matt,
When the address book is synced with Microsoft Exchange, things start working crazy.
When I try to add a custom label to a phone number the code execute correctly but the label is not added to the phone number.
Do you know if there is a way to know when the address book is synced with Microsoft Exchange? In this cases I alert the user that the app doesn’t work properly with contacts synced with an Exchange server.
Thanks
Just thought I’d record some more of my findings:
- as pointed out above, ABAddressBookCopyPeopleWithName isn’t guaranteed to do a match, in cases where you have a suffix or a prefix in the name
- if your user syncs with gmail contacts, or some other service, it’s highly likely that they end up importing contacts which have no first name or last name (for e.g. contacts with just email ids or messenger ids)
- ABAddressBookCopyPeopleWithName returns an array of name matches. So if you have 2 people in your address book with exactly the same name, you will have a hard time trying to figure out which ABRecord your contact is referring to. I haven’t figured out how to best handle this situation yet. Maybe storing more contact details locally (like phone number, email address) is the way to go.
- For testing, you can force your ABRecordIDs to change by syncing with iTunes and ticking the “Replace contacts on iPhone” option.
Have found out a lot of these things by trial and error with my app, Contacts Journal (http://appsto.re/contactsjournal). Hope it helps.