How to perform basic custom contact store operations for Windows Phone 8

[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]

This topic shows you how to create a custom contact store for your app, and how to perform basic operations like adding, updating, and deleting contacts.

This topic contains the following sections.

Note

The custom contact store APIs only provide access to the contacts created by your app. If your app needs read-access to the phone’s contact store or calendar, there are different APIs that you should use. For more information, see Read-only access to Contacts and Calendar for Windows Phone 8.

Creating or opening a contact store

Each Windows Phone app can have a single contact store. You open the store by calling CreateOrOpenAsync. The store will be created when you call this method, if it doesn’t already exist. The overloaded version of this method accepts a member of the ContactStoreSystemAccessMode enumeration and a member of the ContactStoreApplicationAccessMode enumeration, which specify how much access the system and other apps have to your contact store. ContactStoreSystemAccessMode.ReadOnly indicates that only your app can modify your contacts. ContactStoreSystemAccessMode.ReadWrite means that the phone can modify your contacts through its built-in contacts experience. ContactStoreApplicationAccessMode.LimitedReadOnly means that other apps can read only the display name and the profile picture of your contacts. ContactStoreApplicationAccessMode.ReadOnly means that other apps can read all of the properties of your contacts. The default settings are ContactStoreSystemAccessMode.ReadOnly and ContactStoreApplicationAccessMode.LimitedReadOnly

ContactStore store = await ContactStore.CreateOrOpenAsync();
ContactStore store = await ContactStore.CreateOrOpenAsync(
    ContactStoreSystemAccessMode.ReadWrite,
    ContactStoreApplicationAccessMode.ReadOnly);

Adding a contact

After you have opened your contact store, you create a new contact by calling the StoredContact constructor, passing in a reference to your store. The StoredContact object has built-in properties for several common pieces of contact data, such as given name and family name. Each contact also has a field that contains a local ID, which is dynamically assigned by the operating system, and a field for storing a remote ID that can be set by your app. You can use the remote and local IDs to associate contacts in your app’s contact store on the phone with a remote contact store in the cloud.

Warning

If your app uses the ContactStore APIs and uses the StoredContact.RemoteId property to link contacts stored on the phone with contacts stored remotely, it is essential that the value for the RemoteId property is both stable and unique. This means that the remote ID should consistently identify a single user account and should contain a unique tag to guarantee that it does not conflict with the remote IDs of other contacts on the phone, including contacts that are owned by other apps. If you attempt to save a StoredContact that does not have a unique value for the RemoteId property, the save operation may fail. If the remote IDs used by your app are not guaranteed to be stable and unique, you can use the RemoteIdHelper class shown later in this topic in order to add a unique tag to all of your remote IDs before you add them to the system. Or you can choose to not use the RemoteId property at all and instead you create a custom extended property in which to store remote IDs for your contacts.

In addition to the fields that are provided by the StoredContact object, you can add more properties by calling GetExtendedPropertiesAsync. This returns a dictionary of key value pairs that you can populate with contact data. You can use the fields of the KnownContactProperties class for key values if you want the fields to be consistent with the phone’s internal contact property names. Or you can specify any arbitrary string as a key. After you have set the properties on your contact, save it to the store by calling SaveAsync.

The following code shows a simple implementation of adding a contact.

async public void AddContact(string remoteId, string givenName, string familyName, string email, string codeName)
{
    ContactStore store = await ContactStore.CreateOrOpenAsync();

    StoredContact contact = new StoredContact(store);

    RemoteIdHelper remoteIDHelper = new RemoteIdHelper();
    contact.RemoteId = await remoteIDHelper.GetTaggedRemoteId(store, remoteId);

    contact.GivenName = givenName;
    contact.FamilyName = familyName;

    IDictionary<string, object> props = await contact.GetPropertiesAsync();
    props.Add(KnownContactProperties.Email, email);

    IDictionary<string, object> extprops = await contact.GetExtendedPropertiesAsync();
    extprops.Add("Codename", codeName);

    await contact.SaveAsync();

}

Updating a contact

You can update a contact by retrieving it from the store, modifying its properties, and then saving it again. The following code example uses FindContactByRemoteIdAsync to retrieve a contact using its remote ID. You can also call FindContactByLocalIdAsync to retrieve a contact by local ID. Then, the properties of the contact are modified and SaveAsync is called to save the contact back to the store.

async private void UpdateContact(string remoteId, string givenName, string familyName, string email, string codeName)
{
    ContactStore store = await ContactStore.CreateOrOpenAsync();

    RemoteIdHelper remoteIDHelper = new RemoteIdHelper();
    string taggedRemoteId = await remoteIDHelper.GetTaggedRemoteId(store, remoteId);
    StoredContact contact = await store.FindContactByRemoteIdAsync(taggedRemoteId);

    if (contact != null)
    {
        contact.GivenName = givenName;
        contact.FamilyName = familyName;

        IDictionary<string, object> props = await contact.GetPropertiesAsync();
        props[KnownContactProperties.Email] = email;

        IDictionary<string, object> extprops = await contact.GetExtendedPropertiesAsync();
        extprops["Codename"] = codeName;

        await contact.SaveAsync();
    }
}

Deleting a contact

Delete a contact by calling DeleteContactAsync and passing in the local ID of the contact to be deleted. If you want to delete a contact based on the remote ID, call FindContactByRemoteIdAsync then use the value of the Id property to call DeleteContactAsync.

async private void DeleteContact(string id)
{
    ContactStore store = await ContactStore.CreateOrOpenAsync();
    await store.DeleteContactAsync(id);   
}

Querying for contacts

You can query for all contacts in the store by calling CreateContactQuery. The no-argument version of the method returns the default set of fields for each contact, and uses the default ordering. Call GetContactsAsync on the returned ContactQueryResult object to retrieve the list of returned contacts.

async private void DefaultQuery()
{
    ContactStore store = await ContactStore.CreateOrOpenAsync();
    ContactQueryResult result = store.CreateContactQuery();
    IReadOnlyList<StoredContact> contacts = await result.GetContactsAsync();

    ContactListBox.ItemsSource = contacts;

}

If there is a subset of the KnownContactProperties that you know you will need to access, you can create a new ContactQueryOptions and then add property names to the DesiredFields vector to specify that they should be fetched when the query is made. You still must call GetPropertiesAsync to access the field values, but because it does away with extra queries to the database, specifying the desired fields can optimize performance.

You can also set OrderBy to change the field used to order the results, but it is recommended that you use the default ordering because that will provide a consistent user experience across apps.

async private void QueryWithDesiredFields()
{
    ContactStore store = await ContactStore.CreateOrOpenAsync();

    ContactQueryOptions options = new ContactQueryOptions();
    options.DesiredFields.Add(KnownContactProperties.Email);

    ContactQueryResult result = store.CreateContactQuery(options);
    IReadOnlyList<StoredContact> contacts = await result.GetContactsAsync();

    ContactListBox.ItemsSource = contacts;
}

Using the RemoteIdHelper class

As stated above, in order to use the RemoteId field of a custom contact, you must ensure that the ID value is unique across all apps on the phone. If your cloud-based contact store does not use IDs that are guaranteed to be unique, you can add the following class to your project that will help you add a unique tag to your IDs before you save a contact to the store. It will also help you remove the unique tag from the ID after you have retrieved it from the store so that you can get your original remote ID back. The following shows the definition of the RemoteIdHelper class.

class RemoteIdHelper
{
    private const string ContactStoreLocalInstanceIdKey = "LocalInstanceId";

    public async Task SetRemoteIdGuid(ContactStore store)
    {
        IDictionary<string, object> properties;
        properties = await store.LoadExtendedPropertiesAsync().AsTask<IDictionary<string, object>>();
        if (!properties.ContainsKey(ContactStoreLocalInstanceIdKey))
        {
            // the given store does not have a local instance id so set one against store extended properties
            Guid guid = Guid.NewGuid();
            properties.Add(ContactStoreLocalInstanceIdKey, guid.ToString());
            System.Collections.ObjectModel.ReadOnlyDictionary<string, object> readonlyProperties = new System.Collections.ObjectModel.ReadOnlyDictionary<string, object>(properties);
            await store.SaveExtendedPropertiesAsync(readonlyProperties).AsTask();
        }
    }

    public async Task<string> GetTaggedRemoteId(ContactStore store, string remoteId)
    {
        string taggedRemoteId = string.Empty;

        System.Collections.Generic.IDictionary<string, object> properties;
        properties = await store.LoadExtendedPropertiesAsync().AsTask<System.Collections.Generic.IDictionary<string, object>>();
        if (properties.ContainsKey(ContactStoreLocalInstanceIdKey))
        {
            taggedRemoteId = string.Format("{0}_{1}", properties[ContactStoreLocalInstanceIdKey], remoteId);
        }
        else
        {
            // handle error condition
        }

        return taggedRemoteId;
    }

    public async Task<string> GetUntaggedRemoteId(ContactStore store, string taggedRemoteId)
    {
        string remoteId = string.Empty;

        System.Collections.Generic.IDictionary<string, object> properties;
        properties = await store.LoadExtendedPropertiesAsync().AsTask<System.Collections.Generic.IDictionary<string, object>>();
        if (properties.ContainsKey(ContactStoreLocalInstanceIdKey))
        {
            string localInstanceId = properties[ContactStoreLocalInstanceIdKey] as string;
            if (taggedRemoteId.Length > localInstanceId.Length + 1)
            {
                remoteId = taggedRemoteId.Substring(localInstanceId.Length + 1);
            }
        }
        else
        {
            // handle error condition
        }

        return remoteId;
    }

}

Before saving any contacts to the store, create a new RemoteIdHelper and call SetRemoteIdGuid. This creates a GUID for your app and stores it in the extended properties of your contact store. It’s ok if you call this method more than once. If a GUID already exists, it will not be overridden.

ContactStore store = await ContactStore.CreateOrOpenAsync();
RemoteIdHelper remoteIdHelper = new RemoteIdHelper();
await remoteIdHelper.SetRemoteIdGuid(store);

Whenever you need to perform an operation with a remote ID, first pass it into GetTaggedRemoteId to retrieve a copy of the ID that has your app’s GUID appended to it.

If you need to obtain your original remote ID from the remote ID saved in the contact store, call GetUntaggedRemoteId which removes the unique tag and returns the original ID.

string untaggedRemoteId = await remoteIdHelper.GetUntaggedRemoteId(store, taggedRemoteId);
UpdateRemoteContact(untaggedRemoteId, contact);

Saving a contact from a vCard

vCard is a standard file format used for electronic business cards. The ContactInformation class exposes the ParseVcardAsnc method, which asynchronously parses the vCard at the specified URI, and then returns a populated ContactInformation object. Pass this and a reference to your contact store to the StoredContact constructor, and then call SaveAsync to save the contact.

The following sample code shows how to save a contact to your custom contact store from a vCard.

task<StoredContact^> SaveFromVcard(ContactStore^ store, String^ uriToVcard)
{
    // Invoke the parser on the passed-in URI.
    return create_task(ContactInformation::ParseVcardAsync(ref new Uri(uriToVcard)))
    .then([=] (ContactInformation^ info) -> StoredContact^
    {
        // When that's done, wrap a stored contact around it, save.
        auto contact = ref new StoredContact(store, info);
        create_task(contact->SaveAsync()).get();

        return contact;
    });
}

See Also

Other Resources

Custom contact store for Windows Phone 8