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:

Bass Jobsen said...

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

Simon MacDonald said...

@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.

Lior said...

Dear Simon,

Great Article!

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

Lior.

Simon MacDonald said...

@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.

Wallace said...

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

Simon MacDonald said...

@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.

Wallace said...

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!

Simon MacDonald said...

@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.

Unknown said...

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?

Simon MacDonald said...

@Damien

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

Santosh said...

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

Kaustubh said...

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.

Simon MacDonald said...

@Kaustubh

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

Ajinkya Rane said...

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.

Simon MacDonald said...

@Ajinkya

What do you see in "adb logcat"?

Arun said...

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

With regards,
Arun.

Simon MacDonald said...

@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.

Arun said...

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...

Simon MacDonald said...

@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.

Bee said...

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);
}

Bee said...

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

Simon MacDonald said...

@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.

Bee said...

@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?

Simon MacDonald said...

@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.

p said...

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 !

p said...

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.

Simon MacDonald said...

@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.

Unknown said...

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?

Simon MacDonald said...

@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.

Rj said...

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.

Simon MacDonald said...

@Rajesh

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

Rj said...

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

Simon MacDonald said...

@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.

BDSWorks said...

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

Simon MacDonald said...

@BDSWorks

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

Unknown said...

There is a way for remove a number from a phoneNumbers array?

Simon MacDonald said...

@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 "".

Anonymous said...

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!

Simon MacDonald said...

@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.

SimpleSolution said...

@Simon

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

Simon MacDonald said...

@SimpleSolution

Yes

sangeeth_LVS said...

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 ...

Unknown said...

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

Thanks..

kumar.

Simon MacDonald said...

@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.

Simon MacDonald said...

@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

Unknown said...

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!

Simon MacDonald said...

@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.

AnAkiNJavi said...

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.

Mallikarjun C H said...

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

Simon MacDonald said...

@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.

Anonymous said...

@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!

Simon MacDonald said...

@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.

Anonymous said...

@Simon MacDonald

Thanks so much, it's work now!

Anonymous said...

@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

Simon MacDonald said...

@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?

Unknown said...

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

Simon MacDonald said...

@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.

Unknown said...

Ok, Thanks! I'll try!

Abir said...

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

Simon MacDonald said...

@Abir

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

Abir said...

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;

Simon MacDonald said...

@Abir

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