By clicking “Accept All Cookies”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.

Implementing calendar synchronization with Google Calendar API

Facundo Errobidart
August 15, 2023

Intro

Google is renowned for its wide range of APIs, including Maps and Notes, which offer limitless possibilities. However, each service has its own unique quirks that can be time-consuming to navigate.

In this particular case, a client requested the use of their staff's Google Calendar to manage call availability on their platform. In this article we describe the journey of implementing this integration.

Body

To achieve this, every user in the app must log in with their Google account using oAuth2. This step is straightforward, with plenty of documentation available. First, you need to log in to the Google Console, create a new project, and create an API client to obtain your client and secret IDs. Google provides numerous SDKs that make this process relatively simple by providing pre-built classes to construct the client.

Once the client is built, endpoints must be created to complete the oAuth authentication process. These endpoints lead to the "login with Google" and "redirect after login" processes. The endpoints' URIs must then be added to the client created in the console; otherwise, an error message will be received.

Now that we have a Google API client working, we must fetch events and keep them synchronized at all times. The Calendar API provides us with a way to use webhooks. First, we need to create a watch over a specific calendar. This is done upon authentication by assigning an id to the asset we want to "watch". In this case, we use the id of the user logging in since we only look at their primary calendar (a user can have multiple calendar instances in Google Calendar). After this, a webhook can be set up to be called every time the calendar is updated. This webhook should only receive one parameter: the id we passed previously. With that information, we must consume the events API from Calendar.


final NetHttpTransport HTTP_TRANSPORT= GoogleNetHttpTransport.newTrustedTransport();
Calendar calendar = getCalendar(integration, HTTP_TRANSPORT);


Channel channel = new Channel();
channel.setId(integration.getExternalId()); //The external id we use to identify the calendar when updated
channel.setType("web_hook");
channel.setAddress(apiUrl + "/v1/integrations/google/calendar/notification"); //The url where we will receive the notification


Calendar.Events.Watch watch = calendar.events().watch("primary", channel); //Create the request

integration.setListeningSince(new Date()); //We mark this integration as listening
watch.execute(); //Execute the request

Before proceeding, we must understand the concept of incremental sync. Every time we call the Calendar API, we can use an optional parameter called a sync token. Using it, we can prevent the entire calendar from being re-synced and only bring the latest updates. The token is provided in the response body.

We can also set other parameters that are relevant to us, for example there is a flag to make each instance of a recurring event a different entry in the array, if not set, we will only obtain the first appearance of the recurring event and its rule, nothing else. Another caveat is the sync token isn’t compatible with other parameters such as the date bounds to fetch events between certain dates.


Final NetHttpTransport HTTP_TRANSPORT= GoogleNetHttpTransport.newTrustedTransport();
this.refreshTokenIfNeeded(integration);


Calendar calendar = getCalendar(integration, HTTP_TRANSPORT);
Calendar.Events.List request = calendar.events().list("primary");


//Using time min won't bring a sync token
if (integration.getSyncToken() != null) {
  request.setSyncToken(integration.getSyncToken());
}
request.setMaxResults(250);


// make the API unfold the recurring events into instances
request.setSingleEvents(true);

After this, we can look into the events themselves. They have the usual information you would expect from an event, such as start and end dates (sometimes it's a timestamp, and sometimes it's only a date, so handling this can be tricky), attendee list, title, description, etc.
So far so good, but if you are an avid calendar user you will notice that I didn’t mention anything about marking an event like busy or available, here there are other Calendar magic tricks that we experienced by working with this integration. There is a field called “transparency” that accepts the values “transparent” for available and “opaque” for busy, so "this shouldn’t be a problem", we thought. Turns out that those values are only set when the event is marked by the user itself, but by default (which was the majority) the value was set in null and since we only took into account events marked as busy, that was a big issue for the business logic.


List attendees = event.getAttendees() != null ? event.getAttendees() : Collections.emptyList();
Optional calendarAttendee = attendees.stream().filter(a -> a.getEmail().equalsIgnoreCase(doctor.getPerson().getEmail())).findFirst();
boolean accepted = (calendarAttendee.isPresent() && calendarAttendee.get().getResponseStatus().equals("accepted")) || event.getOrganizer().getEmail().equals(doctor.getPerson().getEmail());
boolean busy = event.getTransparency() == null || event.getTransparency().equals("opaque"); //transparency == opaque equals to "busy" in the calendar UI

Conclusion

The Google API library is extremely useful and complete when it comes to functionality and documentation - we tested it particularly in the context of Google Calendar integration. However, being so vast and built over a large amount of time means that there are small details that fall through the cracks and that have been discovered by interacting with the API and develop several iterations of it to reach a robust integration.

Interested in our services?
Please book a call now.