Reading and change tracking contacts on Windows 10

This post walks through creating an app that is able to read the user's contacts on the system. This example just writes the data out to the console, but it should get you started for any whatever your specific application is with this data, whether that's creating an address book app or looking for matches for a messaging experience. 

Go ahead and start a new blank C# UWP project in Visual Studio.

 The first thing you need to do now is declare that your app is going to access the contacts data on the device. You do this by adding "contacts" to the capabilities node of the Package.appxmanifest file. You can do this by editing the XML directly, right click on the Package.appxmanifest file in your project and choose "view code", then just add the following line. (Yes, this should be part of the manifest editor UI, we'll get it in there.)

     <uap:Capability Name="contacts"/>

 Then let's add three buttons to your MainPage.xaml:

  1. Initialize Contact Store
  2. Read Contacts
  3. Listen for Changes

 Add event handlers for the click objects on each of those buttons to the MainPage.cs file.

 1. Initialize contact store: This is going to attempt to open the contacts datastore, ensure that your app has the right permissions, and make sure that the user has not disabled access for your app. Oh, we'll also go ahead and tell the system that you want to watch for changes on these contacts. This lets us know to keep a snapshot of the state of the contacts database right now so that later you can ask for all of the changes that have happened.

 

        ContactStore contactStore = null;

 

        private async void initializeContactsStoreButton_Click(object sender, RoutedEventArgs e)

        {

            //if this is throwing Access Denied, you haven't declared the contacts capability in the manifest

            contactStore = await ContactManager.RequestStoreAsync(ContactStoreAccessType.AllContactsReadOnly);

 

            if (contactStore == null)

            {

                //Could not open the contacts store because the user denied you in settings, sorry!

                MessageDialog connectionWarning =

                    new MessageDialog("The app needs access to your contacts in order to function correctly. " +

                        "Please grant it access using the options in the settings menu ",

                        "Lost Connection to Store");

                await connectionWarning.ShowAsync();

 

                //you should probably ask the user before taking him/her to the settings page, but ya know, sample

                await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-contacts"));

                return;

            }

 

            //Enable change tracking for your app, this tells the system that you'll want to know about changes that happen

            //you only need to call it once, but it no-ops after the first time

            contactStore.ChangeTracker.Enable();

        }

 

2. Read Contacts: Just as it sounds. Let's read all of the contacts in the system. I'm going to just spit them out to the debugger (click view->Output to show that window in Visual Studio), but you'll probably want to do something a little fancier with them: bind them to a list view, create a sqlite cache, compare them with another list that you have, etc. I'm also going to reset the change tracker here. Doing this is telling the system that I don't care about previous changes, because I'm about to read the data set from scratch.

 

        private async void readContactsButton_Click(object sender, RoutedEventArgs e)

        {

            if (contactStore == null)

            {

                MessageDialog storeNotInitializedWarning =

                    new MessageDialog("Please initialize the contact store first.", "Store not initialized");

                await storeNotInitializedWarning.ShowAsync();

                return;

            }

 

            //before you read all of the contacts [again?], you should reset the change tracker

            contactStore.ChangeTracker.Reset();

 

            //ok let's read the contacts

            var contactReader = contactStore.GetContactReader();

 

            var contactBatch = await contactReader.ReadBatchAsync();

 

            while (contactBatch.Contacts.Count != 0)

            {

                foreach (Contact contact in contactBatch.Contacts)

                {

                    //So, what do you want to do with these?

                    Debug.WriteLine(contact.Id + ": " + contact.DisplayName);

                }

 

                contactBatch = await contactReader.ReadBatchAsync();

            }

        }

 

3. Listen for changes: Here we're going to hook up an event handler and whenever there is a change to any of the user's contacts, this event will fire. It's easier to test this on desktop Windows since you can have your app and the People app open at the same time, the latter to make the changes.

 

        private async void listenForContactChangesButton_Click(object sender, RoutedEventArgs e)

        {

            if (contactStore == null)

            {

                MessageDialog storeNotInitializedWarning =

                    new MessageDialog("Please initialize the contact store first.", "Store not initialized");

                await storeNotInitializedWarning.ShowAsync();

                return;

            }

 

            contactStore.ContactChanged += ContactStore_ContactChanged1;

 

        }

 

        private async void ContactStore_ContactChanged1(ContactStore sender, ContactChangedEventArgs args)

        {

            //while this function is open, we won't fire additional change events

            //this allows you to operate on the changes without worrying about simultaneos trackers being open

            //holding the defferal is necessary because of async'ness

            var defferal = args.GetDeferral();

 

            ContactChangeReader reader = sender.ChangeTracker.GetChangeReader();

            IReadOnlyList<ContactChange> changes = await reader.ReadBatchAsync();

            while (changes.Count != 0)

            {

                foreach (ContactChange change in changes)

                {

                    switch (change.ChangeType)

                    {

                        case ContactChangeType.Created:

                            Debug.WriteLine("[created]" + change.Contact.Id + ": " + change.Contact.DisplayName);

                            break;

 

                        case ContactChangeType.Deleted:

                            Debug.WriteLine("[deleted]" + change.Contact.Id + ": " + change.Contact.DisplayName);

                            break;

 

                        case ContactChangeType.Modified:

                            Debug.WriteLine("[modified]" + change.Contact.Id + ": " + change.Contact.DisplayName);

                            break;

 

                        case ContactChangeType.ChangeTrackingLost:

 

                            MessageDialog lostChangeTrackingWarning =

                                new MessageDialog("The system has lost the change tracking information. " +

                                    "This shouldn't happen very often, but you should handle the case. " +

                                    "The expectation is that you'll re-read all of the contacts after this.",

                                    "Change tracking lost");

                            await lostChangeTrackingWarning.ShowAsync();

 

                            defferal.Complete();

                            //Returning since changes are no longer going to be valid after resetting

                            //the change tracking

                            return;

 

                        default:

                            //No-op on the default case for future proofing

                            break;

                    }

                }

                changes = await reader.ReadBatchAsync();

            }

 

            //Let the system know that we have processed all the changes so the same changes aren't surfaced again

            reader.AcceptChanges();

 

            defferal.Complete();

        }

 

So, that will get you a pretty good contacts reading app, as long as your app is in the foreground. If you want to be woken up in the background also, there are just a couple more things that you need to do.

 

1. Add the manifest declaration for the background task

    * Open the manifest editor

    * Go to the declarations tap

    * Add a new Background task

    * Make the supported tasks type general

    * Assign the entry point as something like: "ContactChangedBackgroundTask.BackgroundTask"

    * Save the manifest

2. Create the Windows Runtime Component that gets launched

    * Right click on the solution in the Solution explorer

    * Add -> New Project

    * Choose "Windows Runtime Component (Universal Windows)"

    * And name it to match the entry point above, e.g. "ContactChangedBackgroundTask"

3. Add reference to the Windows Runtime Component from the main application

4. Register tor the background task in your main application, maybe another button and click event handler like the following.

 

        private static string backgroundTaskName = "ContactChangedTask";

 

        private async void registerBackgroundTaskButton_Click(object sender, RoutedEventArgs e)

        {

            string taskEntryPoint = typeof(ContactChangedBackgroundTask.BackgroundTask).ToString();

            if (!IsBGTaskAlreadyRegistered(backgroundTaskName))

            {

                //already registered the task

                return;

            }

 

            var trigger = new ContactStoreNotificationTrigger();

            var access = await BackgroundExecutionManager.RequestAccessAsync();

            if (access == BackgroundAccessStatus.Denied)

            {

                Debug.WriteLine("Access Denied, check your capabilities", "debug");

                return;

            }

 

            var builder = new BackgroundTaskBuilder();

 

            builder.Name = backgroundTaskName;

            builder.TaskEntryPoint = taskEntryPoint;

            builder.SetTrigger(trigger);

 

            BackgroundTaskRegistration task = builder.Register();

        }

 

        private bool IsBGTaskAlreadyRegistered(string taskName)

        {

            bool retVal = true;

            lock (taskName)

            {

                foreach (var cur in BackgroundTaskRegistration.AllTasks)

                {

                    if (cur.Value.Name == taskName)

                    {

                        retVal = false;

                        break;

                    }

                }

            }

            return retVal;

        }

 

5. Add a class to the background component and put some logic in there. If you used the naming above, you'll want to call the class BackgroundTask.cs. Here is something you might want to use, you can update a tile, or pop a toast from this to verify it's running.

 

        public sealed class BackgroundTask : IBackgroundTask

        {

             public void Run(IBackgroundTaskInstance taskInstance)

            {

                //Grab the deferral to make sure that the background task won't be called again until

                //you are finished processing this set of changes

                BackgroundTaskDeferral deferal = taskInstance.GetDeferral();

   

                //Notify the user that a contact has changed.

                //In your app you might want to do some change tracking here or maybe update

                //your server.

 

                deferal.Complete();

            }

        }

 

Well, hopefully that gets you started. Please let me know how it goes and if you have any trouble following this example. 

-tony

 

ps. Yup, I need to figure out how to display the code snippets better. I'll get it updated. :)