Tuesday, September 20, 2011

Saving Contacts with PhoneGap Android

Frequently on the Phonegap Google Group I'm responding to users who have problems saving contacts on Android using PhoneGap. There are a number of steps that you need to follow in order to be able to save a contact on your device/emulator and I thought I'd go over them here so that others can benefit.

First off is the choice of API you use when creating your emulator. Most people will use the "Android" version of the API level they want to target. Amazingly enough this is a bad choice. Say you wanted to target API level 10 you shouldn't select "Android 2.3.3 - API Level 10" instead you should select "Google APIs (Google Inc.) - API Level 10".

Update 2012/02/29: I've fixed an issue in PhoneGap 1.4.1 and you should now be able to save contacts to the local database and not require a Google account anymore.

So let's get started by creating a new AVD:


Once you have a your new Google API AVD setup you'll want to create you PhoneGap application and use the following HTML as your main page:


Once you've got the code in place run the application in the AVD created earlier. You should see the following:


At this point you are probably thinking "well that's not good". Well no it isn't but at least there is a good reason for it. Your AVD has no account so let's go create one. You have to go to Applications -> Settings



Accounts & Sync


Click Add an Account



I recommend using the Google account option as it is the easiest to setup and it's far more likely you have access to a Google account for testing rather than an MS Exchange account.



Select Next


Select Create if you don't have a Google account or Sign In if you already do.


Provide your user id and password, then click Sign In.


You should see the following success screen.


Now click Finish Setup and re-run your application and you should see this screen:


Alright, we did it. Let's head into the Contacts application to make sure that the contact has saved successfully. To which we are greeted with this screen:


Now at this point I've probably lost all credibility with you but wait don't click off the post just yet as this is completely normal. Click the Menu button and select Display Options


Then you'll need to select the new account that you added and click the checkbox to show All Contacts.


Then once you click the Done button you'll be taken back to the Contacts application and you will finally get to see the contact you saved.


That is a bit of a pain to setup but once it is done you won't need to ever do it again as long as you keep your AVD around.

However, there are two other things that trip up people when they are trying to save on contact from a PhoneGap application. The first is that people forget to put the WRITE_CONTACTS permission in their manifest file. You should have this line in your AndroidManifest.xml file:
<uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission>

The second gotcha is that the developer never actually calls the save() method. You see the code:
var contact = navigator.contacts.create();
Does not persist the contact to the internal database. Rather it creates an object contact that is an instance of a Contact. If you want to save your contact to the database you need to call contact.save():
contact.save(successCB, errorCB);
So if you've followed all of these instructions and you still can't seem to get the contact to save come on over to the PhoneGap Google Groups and ask for some help. You might just have found a bug. Please mention details of what the problem is. A bug report called "Contacts are broke" doesn't give anyone enough information to help you.

62 comments:

  1. Hi Simon,

    Thanks for your good article.

    You say to use "Google APIs (Google Inc.) - API Level 10" instead of android 2.3.3.
    Why?
    And what when i want to emulate an earlier version of android? b.e. there is no "Google APIs" with level 7

    Best regards,

    Bass

    ReplyDelete
  2. @Bass Jobsen

    You're right, I didn't explain that very well in the post. Basically it is because the Google APIs allow you to link the AVD to your Google account while the standard Android APIs only give you the option to hook it up to an Exchange server and who has one of those hanging around.

    There is a Google APIs version of each API level available. At least there is when I go look.

    ReplyDelete
  3. Dear Simon,

    Great Article!

    how do i distinguish whether the contact will be saved under "Phone Contacts" or under "Google Contacts"
    ?
    thanks!

    Lior.

    ReplyDelete
  4. @Lior

    The contact will be saved under your Google Contacts. Saving to Phone Contacts is problematic right now and causes an error. That's something I need to go back and take a look at when I have time.

    ReplyDelete
  5. Hi Simon,

    Im using phonegap for android and I want to be able to save contacts displayed in a webview to the native contacts db on the phone. It looks as if this is not possible with the JS libraries as you have stated in this post. I have tried all of the examples given http://docs.phonegap.com/en/1.0.0/phonegap_contacts_contacts.md.html#ContactName . The example alerts display success, but I cant find the contacts when I look for them. I'm thinking I might not be able to do this at this time. My app displays contact information from a corporate LDAP, users can use the links to call the phone numbers listed. The one thing I'm missing is the ability to store the contact information on the natively on the device. Any help would be appreciated!

    Thanks,
    Mark

    ReplyDelete
  6. @Wallace you probably didn't setup your display options to show the contacts. Make sure your gmail or email account is checked in the Contacts app under display options.

    ReplyDelete
  7. Thanks Simon, that worked to get the contacts viewable. Is there anyway to prompt the user before the contacts are stored as to which account they would like the contact saved? When I store a contact manually the phone prompts me with account options i.e. google or touchdown , is this possible with Phonegap?

    Thanks!

    ReplyDelete
  8. @Wallace

    No not yet. The problem I had originally is that Android dialogs are non blocking so I'd need to know what account you want to associate the contact with before the save. Since this is not according to the W3C spec we went with the default of the first email account we find.

    ReplyDelete
  9. Did you ever figure out how to store to a phone without a google account? Currently i'm developing an app that stores contacts to the phone. Seems there should be a way to do it without a google account and if you use this for the iOS, will it natively with without needing something else?

    ReplyDelete
  10. @Damien

    Not yet, I'm hoping to get it fixed before the 1.4 release which will be around Jan 30th.

    ReplyDelete
  11. sir it works fine on first run after creation of emulator but after that it doesnt work. Same error occurs

    ReplyDelete
  12. What can be the reason for showing the message "Unable to ope connection to the server" when I give my google account connection credentials while adding the account??
    I am using android 2.1 platform.
    thanks.

    ReplyDelete
  13. @Kaustubh

    Did you make sure you are using the Google version of the AVD?

    ReplyDelete
  14. hi Simon,
    m trying to save contact, i follow ur steps bt its giving
    Error = undefined

    m using eclipse indigo and phoneGap1.3.0
    with GoogleAPI 2.3.3 plz help.

    ReplyDelete
  15. @Ajinkya

    What do you see in "adb logcat"?

    ReplyDelete
  16. Sir, can you tell me how to run same program in android avd?

    With regards,
    Arun.

    ReplyDelete
  17. @Arun

    Actually, you should be okay with PhoneGap 1.4.1 as I fixed the issue keeping it from being able to save local contacts.

    ReplyDelete
  18. Mr.Simon,

    When I run your program, onDeviceReady() method is not calling.

    And I've followed your steps using Google API.

    It just displaying 'Example' and 'Find Contacts' in my emulator.

    Can you help me...

    ReplyDelete
  19. @Arun

    Well if you are running my example unedited you probably need to change the script tag to load the version of PhoneGap you've downloaded.

    ReplyDelete
  20. Hi Simon,

    Very good article, thanks for that!
    But, I still have questions:

    1) After I saved the contact I was not able to remove it with phonegap. I got "Error = 0". How do we do that?

    2) I could not save the contact on a real device. I saw the success alert message but the contact was not in the contacts list. I used Samsung Galaxy Gio, running Android 2.3.3

    I am working on Mac mini, with Snow Leopard, if that matters. Here is my code:

    Index.html
    ...
    <div data-role="content">
    <p>Lilly Shepard</p>
    <button name="Add" id="addButton" onClick="addContact('Lilly Shepard');">Add Contact</button>
    <button name="Remove" id="removeButton" onClick="removeContact();">Remove Contact</button>
    </div><!-- /content -->

    JS file
    ...
    var contact = null;

    function addContact(contactName) {
    contact = navigator.contacts.create();
    contact.displayName = contactName;
    contact.nickName = contactName;
    contact.save(onSaveSuccess,onSaveError);
    };

    function removeContact() {
    contact.remove(onRemoveSuccess,onRemoveError);
    };

    function onSaveSuccess(contact) {
    alert("Save Success");
    }

    function onSaveError(contactError) {
    alert("Error = " + contactError.message);
    }

    function onRemoveSuccess(contacts) {
    alert("Removal Success");
    }

    function onRemoveError(contactError) {
    alert("Error = " + contactError.message);
    }

    ReplyDelete
  21. Sorry, the error while trying to delete contact is "Error = undefined", not "Error = 0".

    ReplyDelete
  22. @Bee

    1) What does error.code give you? It seems odd that you can't remove the contact.

    2) Make sure you have specified the ability for the Contacts app to show all contacts as per the blog post.

    ReplyDelete
  23. @Simon Mac Donald

    2) You were right. I forgot to set the display option to view all contacts on the device. I did it just for the emulator.
    Thanks for your help :)

    1) Error code = 0, Error message = undefined
    Are there other users experiencing the same problem with removing contacts?

    ReplyDelete
  24. @Bee

    No, I haven't heard anyone else having problems deleting a contact. Run "adb logcat" while reproducing the problem to see what info we can learn from it.

    ReplyDelete
  25. Hello Simon,
    Your post is very explanatory and helps well in creating a new contact with Phonegap...
    the Phpnegap doc says :

    save: Saves a new contact to the device contacts database, or updates an existing contact if a contact with the same id already exists.

    Hence my question is how can I update the existing contact ?
    I tried-> var contact= navigator.contacts.create();
    contact.id= (existing Phonegap ID);
    But the contact does not get updated with the details provided.

    Thanx in advance !

    ReplyDelete
  26. Hello Simon,..
    With reference to previous post..
    contact.save works for updating existing contact as well...

    as I was using create() method
    for update it was causing problem earlier.

    ReplyDelete
  27. @p

    Yeah, don't use the create method again as that would create an entirely new contact. If you lose track of your contact just do a find and the object you get from the find method can be modified and then you do a save.

    ReplyDelete
  28. Hi thanks for your article. If I do a loop on all contacts for edit their numbers, callback functions tell me that contacts are saved, but this is not real. Into directory /data/data/com.android.providers.contacts/databases a contacts2.db-journal was created. When this file disappears, saved process is complete. There is no way to control this via Phonegap?

    ReplyDelete
  29. @Ciro,

    Not to my knowledge. This is the sqlite DB creating the journal file. I guess the transaction is not completed until the file is completely processed and removed. I don't think we can do anything about this in PhoneGap. In the native implementation for editing/saving a contact I'm applying a batch edit to the DB. When it is done it returns the number of rows modified so it should be done at that point but it appears that it isn't.

    ReplyDelete
  30. Hi simon, I am new in phonegap. My problem is when i run this code it shows me error lik:

    TypeError: Result of expression 'navigator.contacts' [undefined] is not an object.
    I didn't find any solution.Please help.

    ReplyDelete
  31. @Rajesh

    You need to give me more than that to go on. Are you waiting for the device ready event?

    ReplyDelete
  32. @simon
    Yes im waiting for device ready event;
    I have totally copied your code and put calling it by onviceready.

    ReplyDelete
  33. @Rajesh

    It should work if you've got the "deviceready" event unless you are using a really old version of PhoneGap where navigator.contacts was at navigator.service.contacts.

    ReplyDelete
  34. When I save a new contact, it shows up properly in my Contacts but I never get to the onSuccess function - here's the error from the log:
    06-12 10:57:40.862: D/CordovaLog(20062): Error in success callback: Contacts4 = TypeError: Cannot read property 'latitude' of undefined
    I'm using eclipse and google api level 8

    ReplyDelete
  35. @BDSWorks

    It looks like your success callback is trying to reference 'latitude' which is out of scope.

    ReplyDelete
  36. There is a way for remove a number from a phoneNumbers array?

    ReplyDelete
  37. @Ciro

    The problem is you cannot just set phoneNumbers = null because if you request a contact via the find command and dont' request "phoneNumbers" as one of the attributes to return it will be set to null. So there is no way to tell if the user has set phoneNumbers to null on purpose or not.

    Each phoneNumber has an id which is compared against the Android contacts DB. If the id exists then we consider it a modification. If no id is set for the phoneNumber it is considered and addition. So the best way to remove a phoneNumber right now is to set it's properties to "".

    ReplyDelete
  38. Hi Simon,

    Thank you for this great instruction!

    I met a strange problem with the call back function. Most of the time, the contact.save() will call the error callback with code 0. In one out of 20 chances, it will call the success callback, but 'console.log("Success="+JSON.stringify(contact));' shows that the returned contact object is another person on my phonebook rather than the one I created just now. The app is built with PG 1.7.0, upgrading to 1.8.1 didn't solve the problem. I am testing it on Samsung galaxy s2 with Android OS 4.0.4. Apperantly another person also got similar question on Android OS 2.3.3 in this thread: https://groups.google.com/d/msg/phonegap/O0nrdrDyieE/8IaSwYdDP-8J

    Can you give us some suggestions on this problem? Thank you!

    ReplyDelete
  39. @Bo Wen

    It has to do with the syncing of the contact with Google. It returns an error since the contact hasn't finished syncing yet. There is an open bug on this issue I have not been able to get to yet.

    ReplyDelete
  40. @Simon

    Instead of phonegap.js, do we now us cordova.js file?

    ReplyDelete
  41. hi simon,

    did u find the solution for saving the contact in local device,instead of saving it in to the google contatcs or is there any possiblity to raise a prompt box asking as to save in local or in google contacts (as in google android API).

    Thanks in Advance ...

    ReplyDelete
  42. Hi Simon,
    How can i save a contacts to the phone's local memory and in google contacts.

    Thanks..

    kumar.

    ReplyDelete
  43. @Vasanth

    The W3C does not specific a way to set what account the contact will be saved in. We start by looking for email accounts and if we don't find any we then default to the phone itself.

    ReplyDelete
  44. @sangeeth_LVS

    Since people want this feature can you raise a ticket on JIRA? If you and @Vasanth comment on it then it will have more weight.

    https://issues.apache.org/jira/browse/CB

    ReplyDelete
  45. Hi Simmon,

    Thanks for your excellent article. We have an issue with contact.save in Android. In iOS works fine, but since we update to cordova 2.3, when we try to save a new contact show the followe error (Eclipse - LogCat):

    Tag: ContactsAccessor
    Text: org.apache.cordova.ContactAccessor 147 rawId is string called 'null'


    and:

    Tag: AndroidRuntime
    Text: java.lang.NumberFormatException: unable to parse 'null' as integer

    We try to set Id integer value to contact and no works.

    The same code works so fine in iOS but no works in Android.

    This is a bug or we are doing the wrong things?

    Thanks! Best regards!!!

    Javier!

    ReplyDelete
  46. @Javier Velez

    Can you post some code that reproduces the bug? I have a feeling I may know what is going on but I'd like to see your code to make sure I have the right case. Another dev added some code to verify that the right data types were being passed in.

    ReplyDelete
  47. Hello everyone!

    I was trying to get rid of and error with the PG contact API.

    I followed the API Doc instructions, but kept getting "error = 0" when I save a contact.

    I figured out that the contact actually saves as a "hotmail contact", so after i removed the Hotmail app from my phone, the code worked fine.

    Maybe any other "contact manager" application interfere like the hotmail one.

    Hope it helps someone.

    ReplyDelete
  48. Hi thanks for your good article.
    How to check weather google(gmail) account added in emulator or not?
    please reply

    ReplyDelete
  49. @Mallikarjun C H

    If you follow this post it describes how to setup a new account. To check to see if there is already an account setup you just need to look at the Accounts section of the Contacts app.

    ReplyDelete
  50. @Simon MacDonald

    I have same problem like Javier Velez...

    java.lang.NumberFormatException: unable to parse 'null' as integer

    when i try set id and update contact:

    contact = navigator.contacts.create({
    id: model.contact.list[id].id,
    displayName: displayName,
    name: namex,
    nickname: nickname,
    organizations: organizations,
    photos: contactImgs,
    phoneNumbers: contactPhoneNumbers,
    note: note
    });
    contact.save(function(result){
    //some staff...
    },function(error){
    console.log('save' + error.code)
    });

    please help!

    ReplyDelete
  51. @Pawel G

    If you want to updated an existing contact you should do a navigator.contacts.find and in that success call you would modify the contacts attributes and then call contact.save(). Don't create a new contact and try to match the ID's.

    ReplyDelete
  52. @Simon MacDonald

    Thanks so much, it's work now!

    ReplyDelete
  53. @Simon MacDonald

    Now have another problem with remove contact...
    Before "repair" update contact function, remove was working fine like this:

    var contact = navigator.contacts.create({
    id: id
    });
    contact.remove(function(){
    //...
    },function(error){
    //error
    });

    After change update contact code its not work, so i change this like update contakt code to:

    var options = new ContactFindOptions();
    options.filter=id;
    options.multiple=false;
    var fields = ["id", "displayName", "name", "nickname", "phoneNumbers", "photos", "emails", "addresses", "ims", "organizations", "birthday", "note", "categories", "urls"];
    navigator.contacts.find(fields, function(contacts) {
    if(typeof(contacts[0]) !== "undefined" && contacts[0] !== null)
    alert(contacts[0].id);
    contacts[0].remove(function(){
    //...
    },function(error){
    alert(error.code);
    })}, function(error){
    alert(error.code)}, options);

    And it's not work, i get error.code = 0 from remove()
    What is wrong with my code?

    PS. it work on emulator but not on real device and i have no any app - what sugest @Javier Gil Romero

    ReplyDelete
  54. @Pawel G

    It's is interesting that it works on the emulator. Have you tried seeing what "adb logcat" reports when you try it on the device?

    ReplyDelete
  55. Hey Simon,

    Your article helps big time!
    The only problem I have is that on a real phone, although the contact is getting saved, it still calls "onSaveError". And that's a pitty, because I am trying to save contacts and keep their id's stored in an other file so I could be able to easily remove them later on. (I want to work with id's because their could be multiple "John Johnsons" as a person).
    On the simulator, I could easily get the ID in the "onSaveSuccess" by calling contact.id, but on a real device it saves but doesn't run "OnSaveSucces".

    Any ideas where the problem could be?

    Thanks

    Pieterjan

    ReplyDelete
  56. @Pieterjan De Feyter

    Yes, I've seen this before. The error callback is invoked because the contact has not yet synced with Googles services. If you won't have the ID at that point you should do a search for the contact by name to get the ID.

    ReplyDelete
  57. How can i save contact photo ? I've passed the local URL wich point to the image folder in my app but it doesn't work :(

    Thanks a lot

    ReplyDelete
  58. @Abir

    Show your code, I'm not sure I understand what you are asking.

    ReplyDelete
  59. Good morning Simon,
    This is the code:

    var contact = navigator.contacts.create();

    // Contact Photos
    var photos = [];

    var ImgUrl="images/Thomas.jpg";

    photos[0] = new ContactField('url', ImgUrl,true);
    contact.photos = photos;

    ReplyDelete
  60. @Abir

    Use the full path to your image and it should be okay.

    ReplyDelete