Amazon Alexa Display Support with Next Bus Skill

Introduction

While Amazon Alexa Skills are predominantly verbal based applications, there has been much focus on support for displaying responses visually with a wide spectrum of devices. While an Alexa skill response is verbal, extending functionality to utilize display templates significantly improves the experience with a skill. Whether it is an Echo Show or a device running the Amazon Alexa App, display templates can be utilized in different ways to show the verbal skill response visually.

In a previous blog, we discussed and implemented a simple Amazon Alexa Skill called Next Bus. The Next Bus skill works on any Amazon Alexa device and listens for a bus number and direction from the user. It then calls the Chicago Transit Authority’s Bus Tracker API endpoint to receive and announce the next bus arrival time.

In this blog, we will be extending from our initial Next Bus Alexa Skill MVP by discussing and implementing the following features:

  1. Support the inclusion of a card in the response
  2. Support using a body display template for Echo Show devices with the first arrival time
  3. Support using a list display template for Echo Show devices with multiple arrival times

Note: if following the code examples enclosed, it is recommended to read through and follow along with the previous blog.

Simple Card Support

Extending from the Next Bus skill we developed in the previous blog, we’re going to start by adding a card response alongside the skill’s verbal response. Card responses are used primarily within the Amazon Alexa App but are also supported by Echo Show devices when display templates are unavailable. We’ll be implementing the usage of display templates later in the blog, so we’ll go ahead and support the card display functionality first.

Cards are straightforward to add to an existing Amazon Alexa Skill. There are two primary types of cards: Simple cards and Standard cards. A Simple card consists of a title and text. A Standard card extends from a Simple card, adding an image. Let’s implement the use of a Simple card since we don’t have specific images related to the next bus time.

There is a method in the ResponseBuilder that we can use to add a Simple card to the response named withSimpleCard. We simply need to pass a title and text to this method when building our response. In the code for Next Bus, we can add a variable called speakOutputTitle to store our title (line 29), and pass the speakOutput as our text to the withSimpleCard methodΒ (line 55):

let speakOutput;
const speakOutputTitle = 'Next Bus';

// Check to ensure there is a 'bustime-response' object
if(response && response.data && response.data['bustime-response']){

  // Check to ensure there are available prediction times
  if(response.data['bustime-response'].prd && 0 < response.data['bustime-response'].prd.length){

    // Extract the next prediction time
    let nextTime = response.data['bustime-response'].prd[0].prdctdn;

    // Construct the next bus arrival speech output with the given time retrieved
    speakOutput = `${nextTime} minutes until the next ${busDirection}bound ${busNumber} bus.`;

  }else if (response.data['bustime-response'].error && 0 < response.data['bustime-response'].error.length){
    // If in this block, there are no available next arrival times for the given bus stop
    speakOutput = `No available arrival times for bus #${busNumber} ${busDirection}bound.`;
  }else{
    speakOutput = `An error has occurred while retrieving next time for bus #${busNumber} ${busDirection}bound.`;
  }
}else{
  speakOutput = `An error has occurred while retrieving next time for bus #${busNumber} ${busDirection}bound.`;
}

return handlerInput.responseBuilder
  .speak(speakOutput)
  .withSimpleCard(speakOutputTitle, speakOutput)
  .withShouldEndSession(true)
  .getResponse();

Now, after saving and deploying the code change, we’ll see the card being reflected in the Amazon Alexa App upon making a query:

 

 

 

 

 

 

 

 

 

 

We’ve successfully added support for the Simple card display!

Body Display Template Support

We’ve implemented the Simple card display for the Amazon Alexa App, but now let’s focus on display template support for Amazon Echo Show devices. The display interface both supports displaying information and allows users to interact with a skill via the display. While we are only displaying information, we need to enable the intents used by the display interface before we can move forward. Under the Alexa Skill dashboard, on the tab “Build“, click into the navigation panel for “Interfaces“. Here, there should be a toggle switch for “Display Interface“. Ensure the toggle is switched “on“, then click “Save Interfaces” and “Build Model“. After enabling the “Display Interface“, our skill should now be able to support the use of display templates for Echo Show devices.

Display Support Detection

Before we jump into the code for our display template, we need to add a function to detect whether or not a display is supported. Currently, if we add a display template to a response working with a non-display device [such as an Echo Dot] we will experience an error.

In our code, we can add the following helper function to detect display support:

// Returns true if the skill is running on a device with a display (Echo Show or Spot); false otherwise
const supportsDisplay = function (handlerInput) {
  return handlerInput.requestEnvelope.context &&
    handlerInput.requestEnvelope.context.System &&
    handlerInput.requestEnvelope.context.System.device &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces &&
    handlerInput.requestEnvelope.context.System.device.supportedInterfaces.Display;
};

The function above returns true if a display is supported by the current device, and false otherwise.

Display Template Creation

The Alexa Skill development kit includes various default display templates that can be utilized quickly. There are a variety of body and list styled templates available. We are first going to utilize the first body template (BodyTemplate1), then modify the skill to find multiple next bus prediction times and use a list template.

BodyTemplate1 accepts a few different parameters. Although it is possible to include support for a back buttonΒ and add a token, we’re only going to focus on adding a title, text content, and a background image.

Let’s define the following helper function that accepts a title and text, returning our BodyTemplate1:

// Given strings for title and text, return a BodyTemplate1 object
const getSimpleBodyTemplate = function (title, text) {
  const templateText = new Alexa.RichTextContentHelper()
    .withPrimaryText(text)
    .getTextContent();
  return {
    type: "BodyTemplate1",
    title: title,
    textContent: templateText
  };
};

In the function, we’re accepting a title and text as strings to populate our BodyTemplate1 display template. With regards to the body text, the template accepts a TextContent object. This object allows for a primary, secondary, and tertiary text. But, we’re only going to use the primary text. The Alexa Skills kit includes a helper (RichTextContentHelper) to obtain a TextContent object given the string. In our code, we’re using the helper function to get and store our TextContent object into templateText. The title parameter included under BodyTemplate1 accepts string, so no additional action is required for the title.

Our initial function builds a simple BodyTemplate1 display template and is ready for use.

Adding Template to Response Builder

Given our helper functions, we can now easily add our display template to the skill’s response!

First, let’s define displayTemplate below speakOutput. We’ll then use our previously declared title (speakOutputTitle) and the value Alexa speaks (speakOutput) to generate our display template:

let speakOutput;
let displayTemplate;
const speakOutputTitle = 'Next Bus';

// Check to ensure there is a 'bustime-response' object
if(response && response.data && response.data['bustime-response']){

  // Check to ensure there are available prediction times
  if(response.data['bustime-response'].prd && 0 < response.data['bustime-response'].prd.length){

    // Extract the next prediction time
    let nextTime = response.data['bustime-response'].prd[0].prdctdn;

    // Construct the next bus arrival speech output with the given time retrieved
    speakOutput = `${nextTime} minutes until the next ${busDirection}bound ${busNumber} bus.`;

  }else if (response.data['bustime-response'].error && 0 < response.data['bustime-response'].error.length){
    // If in this block, there are no available next arrival times for the given bus stop
    speakOutput = `No available arrival times for bus #${busNumber} ${busDirection}bound.`;
  }else{
    speakOutput = `An error has occurred while retrieving next time for bus #${busNumber} ${busDirection}bound.`;
  }
}else{
  speakOutput = `An error has occurred while retrieving next time for bus #${busNumber} ${busDirection}bound.`;
}

// Given the title and speakOutput, get the display template
displayTemplate = getSimpleBodyTemplate(speakOutputTitle, speakOutput);

Secondly, we need to alter the way we’re returning the responseBuilder to conditionally include the display template if supported. We can accomplish this as such:

// Given the title and speakOutput, get the display template
displayTemplate = getSimpleBodyTemplate(speakOutputTitle, speakOutput);

handlerInput.responseBuilder
  .speak(speakOutput)
  .withSimpleCard(speakOutputTitle, speakOutput)
  .withShouldEndSession(true);

// Check to see if a display is supported, if so add the display template to the response
if (supportsDisplay(handlerInput) && displayTemplate){
  handlerInput.responseBuilder.addRenderTemplateDirective(displayTemplate);
}

return handlerInput.responseBuilder.getResponse();

We’re first performing our normal functionality of adding the speech output, simple card, and declaring that the session should end. Then, we’re conditionally checking for display support and using the responseBuilder‘s method addRenderTemplateDirective to add our displayTemplate to the response. Finally, we’re returning the response via responseBuilder‘s getResponse method.

After saving and deploying our code change, we can now go into the Alexa Skill testing area to view our display template addition. Before testing, just ensure the “Device Display” checkbox is checked to include a display in testing. When prompting the next bus time for forty-nine southbound we should see a display similar to the following:

Our initial display is looking good, but our body text does appear to be a bit small. Let’s resolve this next.

Rich Text Syntax

The Alexa Skill’s Kit supports a degree of rich text markup for straightforward styling and enhancements. In our function getSimpleBodyTemplate, we’re using the rich text helper (RichTextContentHelper) to form our TextContent object for the template.

Initially, we just passed the text string that represents our body text. There is markup that we can apply around this text parameter however that allows us to style appropriately. There are various options documented here, but we are simply going to utilize the ability to increase the font size of the body text.

We can change our function getSimpleBodyTemplate to wrap the text parameter being passed to RichTextContentHelper via withPrimaryText with the syntax required to increase the font size:

// Given strings for title and text, return a BodyTemplate1 object
const getSimpleBodyTemplate = function (title, text) {
  const templateText = new Alexa.RichTextContentHelper()
    .withPrimaryText(`<font size="7">${text}</font>`)
    .getTextContent();
  return {
    type: "BodyTemplate1",
    title: title,
    textContent: templateText
  };
};

Let’s now save, deploy, and retest the skill with the font size increase we’ve made:

Our initial display is looking great! The body text is now larger and utilizes more of the display to allow for easier reading from afar.

Background Image Inclusion

To extend our initial display template a bit further, we can add a background image to help give the skill a little more depth. The display template (BodyTemplate1) also accepts a parameter for a background image. The Alexa Skill’s Kit also includes a helper that assists in creating the image object required in the template (ImageHelper).

We can alter the getSimpleBodyTemplate function to accept a parameter for the background image URL, and include the background image object required to show in the template:

// Given strings for title, text, and the background image; return a BodyTemplate1 object
const getSimpleBodyTemplate = function (title, text, imgUrl) {
  const templateText = new Alexa.RichTextContentHelper()
    .withPrimaryText(`<font size="7">${text}</font>`)
    .getTextContent();
  const templateImage = new Alexa.ImageHelper()
    .addImageInstance(imgUrl)
    .getImage();
  return {
    type: "BodyTemplate1",
    title: title,
    textContent: templateText,
    backgroundImage: templateImage
  };
};

Above, we’re passing the background image URL to imgUrl. Then, using the image helper provided in the Alexa Skills Kit to create the image object (templateImage) to then pass into our display template.

Now, we can update our code to include the background image URL in the variable displayTemplateBackgroundImage:

let speakOutput;
let displayTemplate;
const displayTemplateBackgroundImage = 'https://engineering.icf.com/wp-content/uploads/2020/05/ICFBlogAlexaNextBusBG.png';
const speakOutputTitle = 'Next Bus';

And pass displayTemplateBackgroundImage into getSimpleBodyTemplate:

displayTemplate = getSimpleBodyTemplate(speakOutputTitle, speakOutput, displayTemplateBackgroundImage);

After saving and deploying the code changes, we should be ready to test our new background image:

Our background image looks fantastic! Much better than the neutral grey background seen by default. Adding a background image is a simple and easy way to spice up a skill. Our body display template is now in a great spot!

List Display Template Support

While our skill now supports the display of the next immediate bus arrival time with a body template, we’re going to extend the skill to announce all of the available next bus arrival times using a list template. List templates allow for dynamic information to be shown easily in conjunction with a skill. They allow for scrolling and can display multiple items more efficiently than a standard body template usually can.

Support for Multiple Times

Before we start the implementation with the list template, we need to convert the current skill to support gathering multiple next arrival times, rather than just capturing the next immediate arrival time.

The API returns the next bus arrival times in an array of predictions. Each object in the array has an attribute named prdctdn that represents the number of minutes until the next bus. Let’s define a function named getPredictionValues that accepts the predictions array from the endpoint and returns an array with the values of string with “min” appended:

// Given an array of predictions returned from the CTA Endpoint, return an array of string representing the next arrival times
const getPredictionValues = function (predictions) {
  let predictionValues = [];
  predictions.forEach((prediction) => {
    predictionValues.push(`${prediction.prdctdn} min`);
  });
  return predictionValues;
};

Next, let’s change the logic of the code to gather multiple predictions instead of only the first immediate prediction. We can delete the nextTime variable and replace with the following code excerpt:

if(response.data['bustime-response'].prd && 0 < response.data['bustime-response'].prd.length){

  // Get the prediction value strings and store in an array
  let predictionValues = getPredictionValues(response.data['bustime-response'].prd);

  // Join the prediction value strings into one variable, appending the commas in-between
  let predictionsOutput = predictionValues.join(', ').replace(/, ([^,]*)$/, ', and $1');

  // Construct the next bus arrival speech output with the given times retrieved
  speakOutput = `${predictionsOutput} until the next ${busDirection}bound ${busNumber} bus.`;

}else if (response.data['bustime-response'].error && 0 < response.data['bustime-response'].error.length){

We’re storing the array of prediction strings in predictionValues, then forming one string that represents the array as a verbal statement in the variable predictionsOutput. Our speech output value speakOutput is then using the new predictionsOutput string to be spoken aloud and displayed on our current body template.

Upon saving and deploying the code, we should now have support for multiple next bus arrival times:

Our skill now supports gathering all available next bus arrival times. We’re now in a great spot to extend even further with a list display template.

List Display Template Implementation

Implementing a list display template for an Alexa Skill is not too different from using a body display template. The main difference is that we’ll be passing in an array of list items rather than the body text. The default list template (ListTemplate1) will then iterate through our array of items and dynamically display them in a scrollable and uniform manner.

List Items for Display Template

First, let’s define a function named getTemplateListItems which will accept our prediction values, bus number, bus direction and return the list items in an array:

// Given the bus prediction values array, bus number, and bus direction return the array of template list items
const getTemplateListItems = function (predictionValues, busNumber, busDirection) {
  let listItems = [];
  predictionValues.forEach((prediction, index) => {
    let templateText = new Alexa.RichTextContentHelper()
      .withPrimaryText(`${busNumber}`)
      .withSecondaryText(`${busDirection.charAt(0).toUpperCase() + busDirection.slice(1)}bound`)
      .withTertiaryText(prediction)
      .getTextContent();
    let listItem = {
      textContent: templateText
    };
    listItems.push(listItem);
  });
  return listItems;
};

In the method getTemplateListItems, we’re able to iterate through the passed prediction times creating a list item object for each. The list item object (ListItem) we’re creating simply consists of a text content object (TextContent). Similar to what we saw earlier, we’re using the helper methods provided in the Alexa Skill’s kit [from RichTextContentHelper] to create the text content object for each item. This time however, we are declaring three different text values: primary, secondary, and tertiary. These different text values are spaced out individually in the list display template as we will see soon.

List Display Template Creation

Next, let’s define a method named getSimpleListTemplate which will accept our skill’s title, array of list items created by getTemplateListItems, and our background image to return our list display template:

// Given the title, list items, and the background image, return a ListTemplate1 object
const getSimpleListTemplate = function (title, listItems, imgUrl) {
  const templateImage = new Alexa.ImageHelper()
    .addImageInstance(imgUrl)
    .getImage();
  return {
    type: "ListTemplate1",
    title: title,
    listItems: listItems,
    backgroundImage: templateImage
  };
};

The method getSimpleListTemplate is similar to getSimpleBodyTemplate, except we’re trading the body text parameter for our array of list items. We’re still using the title and background image, then simply changing the type of the template to ListTemplate1.

Integrating the List Display Template

Now that our methods to generate the list template are defined, we just need to get the list items then set the displayTemplate to use the list template:

if(response.data['bustime-response'].prd && 0 < response.data['bustime-response'].prd.length){

  // Get the prediction value strings and store in an array
  let predictionValues = getPredictionValues(response.data['bustime-response'].prd);

  // Join the prediction value strings into one variable, appending the commas in-between
  let predictionsOutput = predictionValues.join(', ').replace(/, ([^,]*)$/, ', and $1');

  // Construct the next bus arrival speech output with the given times retrieved
  speakOutput = `${predictionsOutput} until the next ${busDirection}bound ${busNumber} bus.`;

  // Get the array of list items from the predictions for the list display template to use
  let predictionListItems = getTemplateListItems(predictionValues, busNumber, busDirection);

  // Given the title, prediction list items, and background image store the list display template
  displayTemplate = getSimpleListTemplate(speakOutputTitle, predictionListItems, displayTemplateBackgroundImage);

}else if (response.data['bustime-response'].error && 0 < response.data['bustime-response'].error.length){

And finally, we simply need to conditionally wrap the displayTemplate we’re setting by default since our list template is being generated as an override:

if(!displayTemplate){
  displayTemplate = getSimpleBodyTemplate(speakOutputTitle, speakOutput, displayTemplateBackgroundImage);
}

Once we save, deploy, and test we should now see the list template in action:

Whooo!! 🎊

Our list display template is now working and displays all of the next bus arrival times!

Conclusion

Whew, we’re done! We’ve successfully extended our Next Bus skill to support multiple next bus times and show the results visually with display templates.

While we have discussed some foundational topics with regards to Alexa Skill display templates, there is much left to dive into and learn. The Alexa Display Interface Reference documentation is a fantastic resource to refer to for continued learning and more advanced topics.

————————–

Note: this Alexa Skill is not a published [public] skill; only for tutorial purposes.

Leave a Reply

avatar
  Subscribe  
Notify of