Calendar integration in UWP apps

In this article I describe some typical calendar integration requirements and hold these against the Windows 10 Universal Windows Platform API’s. I tried to build an app that:

  • opens the calendar at a specific date and time,
  • creates a new appointment,
  • saves an appointment in the calendar,
  • displays a list of the appointments that it created,
  • opens a selected appointment from the list,
  • deletes a selected appointment from the list, and
  • deletes all its appointments,
  • uses roaming, and
  • adhere to MVVM principles.

Here’s how that app looks like - the mega-GUID is an appointment identifier from the list:

 calendar1

A while ago, I wrote a blog post about using the Calendar in Universal Windows 8.1 apps. I observed that Windows Phone and Windows Runtime required a totally different –in terms of object model as well as functionality- client side API to talk to the Calendar. I then decided to not implement calendar integration in my apps, at least not in the Windows 8.* versions. So let’s see if there’s some improvement. Apparently the API that in Windows 8 only worked on the phone, now really became universal. All members of the Windows.ApplicationModel.Appointments namespace are usable on desktop and tablet (and probably other devices). In the sample app, I wrapped all calendar interaction in an MVVM-inspired service:

calendar2

Opening the user’s Calendar

It makes sense for an app to open the user’s calendar at a specific date and time, e.g. to check availability. This is done with the AppointmentManager.ShowTimeFrameAsync method. Here’s the call in the service:

public async static Task Open(DateTimeOffset dto, TimeSpan ts)
{
    await AppointmentManager.ShowTimeFrameAsync(dto, ts);
}

And here’s how the viewmodel calls the service:

private async void Open_Executed()
{
    var tomorrow = DateTime.Now.AddDays(1);
    var duration = TimeSpan.FromHours(24);
    await Calendar.Open(tomorrow, duration);
    Toast.ShowInfo("Your calendar app should be open now.");
}

This is the result at runtime:

calendar3

Creating a new appointment

All you need to do in your app to create an appointment … is create an Appointment. This is a very rich class that allows you to completely configure a calendar entry: date and time, location, description, sensitivity, recurrence, invitees, reminders and what not:

var appointment = new Appointment();
appointment.StartTime = DateTime.Now.AddDays(1);
appointment.Duration = TimeSpan.FromHours(1);
appointment.Subject = "Five Cents Session";
appointment.Location = "Lucy's psychiatric booth";
// Warning: you don't get an ID back with these:
// appointment.Invitees.Add(new AppointmentInvitee());
// appointment.Invitees[0].DisplayName = "Lucy van Pelt";
// appointment.Invitees[0].Address = "lucy@peanuts.com";
// appointment.Organizer = new AppointmentOrganizer();
// appointment.Organizer.DisplayName = "Lucy van Pelt";
// appointment.Organizer.Address = "lucy@peanuts.com";
appointment.Sensitivity = AppointmentSensitivity.Private;
appointment.BusyStatus = AppointmentBusyStatus.OutOfOffice;
appointment.Details = "I need to discuss my fear of the Kite-Eating Tree with someone I can trust.";
appointment.Reminder = TimeSpan.FromMinutes(15);

Adding an appointment to the calendar

When your app instantiates an appointment, it’s not in the calendar yet. This is done with a call to AppointmentManager.ShowAddAppointmentAsync. One of the parameters in this call is the rectangle “around which the operating system displays the Add Appointment UI”. That was useful in Windows 8 where that Appointment UI was a dialog. In Windows 10 the entire Calendar app is launched, so I guess that the rectangle parameter is just there for backward compatibility – and to annoy MVVM developers who are forced to deal with UI coordinates in a viewmodel Annoyed. The call opens an entry form in the calendar app. When the user saves the appointment, then a local identifier is returned.

Here’s the MVVM service code. When an identifier is returned, it is immediately added to a list that is roamed for the app:

public async static Task<string> Add(Appointment appt)
{
    var selection = new Rect(new Point(Window.Current.Bounds.Width / 2, Window.Current.Bounds.Height / 2), new Size());
    return await Add(appt, selection);
}

public async static Task<string> Add(Appointment appt, Rect selection)
{
    var id = await AppointmentManager.ShowAddAppointmentAsync(appt, selection, Placement.Default);
    AddAppointmentId(id);

    return id;
}

public static void AddAppointmentId(string appointmentId)
{
    if (String.IsNullOrEmpty(appointmentId))
    {
        return;
    }

    string ids = ApplicationData.Current.RoamingSettings.Values["AppointmentIds"] as string;
    if (String.IsNullOrEmpty(ids))
    {
        ids = appointmentId;
    }
    else
    {
        ids += ";" + appointmentId;
    }

    ApplicationData.Current.RoamingSettings.Values["AppointmentIds"] = ids;
}

warning Meetings are no Appointments

The Appointment class allows you to add an Organizer and Invitees. When you do this, then the appointment becomes a meeting. When you hit the ‘send’ button in the UI, then ShowAddAppointmentAsync return with an exception. This behavior is documented in the API:

calendar4

Here’s the call from the client viewmodel, including exception handling and notification:

try
{
    var appointmentId = await Calendar.Add(appointment);

    if (appointmentId != String.Empty)
    {
        this.OnPropertyChanged(nameof(AppointmentIds));
        Toast.ShowInfo("Thanks for saving the appointment.");
    }
    else
    {
        Toast.ShowError("You decided not to save the appointment.");
    }
}
catch (Exception ex)
{
    Log.Error(ex.Message, nameof(Add_Executed));
    Toast.ShowError("I'm not sure if this appointment was added.");
}

When you want to allow the user to fine tune the new Appointment in the Calendar app before saving, then you should call the AppointmentManager.ShowEditNewAppointmentAsync method. That makes the Calendar UI editable (at least on the phone – on the desktop it seems to be always editable). Here’s how adding a non-editable appointment looks like on the Windows Phone emulator:

calendar6

Showing, editing, and deleting an appointment

There are three things your app can do with a local identifier for an appointment:

The Calendar MVVM service in the sample app just passes these calls. Here’s the Display method:

public async static Task Display(string appointmentId)
{
    await AppointmentManager.ShowAppointmentDetailsAsync(appointmentId);
}

The Delete method is more complex, since it does exception handling and needs to update the roaming list of appointments. Oh, and it needs to hick up that rectangle again:

public async static Task<bool> Delete(string appointmentId, bool ignoreExceptions = false)
{
    var selection = new Rect(new Point(Window.Current.Bounds.Width / 2, Window.Current.Bounds.Height / 2), new Size());
    try
    {
        var success = await AppointmentManager.ShowRemoveAppointmentAsync(appointmentId, selection);
        if (success)
        {
            RemoveAppointmentId(appointmentId);
        }

        return success;
    }
    catch (Exception)
    {
        if (!ignoreExceptions)
        {
            throw;
        }
        else
        {
            RemoveAppointmentId(appointmentId);
            return true;
        }
    }
}

public static void RemoveAppointmentId(string appointmentId)
{
    var remainingAppts = AppointmentIds;
    remainingAppts.Remove(appointmentId);
    var newIds = string.Join(";", remainingAppts);
    ApplicationData.Current.RoamingSettings.Values["AppointmentIds"] = newIds;
}

And here’s one of the service calls in the view model:

private async void Delete_Executed()
{
    try
    {
        await Calendar.Delete(SelectedAppointmentId);
        this.OnPropertyChanged(nameof(AppointmentIds));
    }
    catch (Exception ex)
    {
        Log.Error(ex.Message, nameof(Delete_Executed));
        Toast.ShowError("I'm not sure if the appointment was deleted.");
    }
}

Here’s the UI – which is always similar: the Calendar app is opened op top of your app.

calendar5

warning Replacing is not Editing

When you modify an appointment (change data or reschedule) that was created with your app, the local identifier remains the same. Even if you edit that appointment from another device. A call to AppointmentManager.ShowReplaceAppointmentAsync, however, will return a brand new identifier value when you changed the appointment. It will return null if you didn’t modify the appointment. After all, the method is not called ShowEditAppointmentAsync

Some final remarks

The Universal Windows 10 Platform Calendar API is an improvement over the Windows 8.1 Universal App version, in the sense that it’s finally Universal: the API is the same for all device types. There is however still some room for improvement.

  • The only thing that your app really knows about a appointment that it created, is its identifier. It’s not possible to get the details of an appointment. I may have missed something, but unless you start creating custom calendars the following code –against existing API’s- doesn’t work:

var store = await AppointmentManager.RequestStoreAsync(AppointmentStoreAccessType.AppCalendarsReadWrite);
var appt = await store.GetAppointmentAsync(appointmentId);
  •  I can’t confirm that the roaming works (simply because I don’t have enough hardware to test it out). The Windows 8.1 documentation consistently described the appointment identifiers as ‘device specific’ (so it did not make sense to roam these). That mention is gone now, and the comments in the code samples in the new documentation seem to suggest that the identifiers may be used from other devices. That would be absolutely nice, but –as I already stated- I can’t confirm it yet. By the way, Appointment comes with a read/write RoamingId property. But when I assign a value to, I get an exception.

Code

The entire source code for the sample app is here on GitHub. If you want another working example, then check out the official Appointment Calendar Sample by Microsoft.

Enjoy!

XAML Brewer