article

Pan@Amazon avatar image
Pan@Amazon posted

Introduction to Alexa Presentation Language (APL) and Multimodal Skills

Overview

In this tutorial, we will go over a Hello World example using Alexa Presentation Language (APL) to display static text on different Echo devices with displays, such as Echo Show, Echo Dot and Fire TV Cube. We will be using the APL Authoring Tool to assist us in generating an APL document for a skill to render visuals on displays when present.

Here is what we will go over in the tutorial:

  • Creating an initial Hello World APL document
  • Rendering the APL document to a display
  • Conditionally display visuals depending on the lack or presence of a display

What is APL?

Alexa skills now support multimodal display capabilities that can enhance your voice experience. Visual complements to your skills could be built through the Alexa Presentation Language (APL). The developer console provides an Authoring Tool that let's you add components and update values to generate an APL document which stores layout information in JSON format. The APL document could also be created through raw JSON as well.

Behind the Scenes (Optional)

When getting started with APL, it is helpful to know that the underlying layout mechanism is based on a subset of CSS Flexbox rendered by the Yoga layout engine. Flexbox provides a grid-like layout for elements on a page, so think rows and columns. It's simple, yet flexible.

Creating an APL Document with the Authoring Tool

Below is a document that will display static content, in our case Hello World. We'll get into a more useful example in future guides.

a) Go to the Alexa Developer Console

b) Create a new skill

c) Click on Interfaces on the left menu

d) Toggle the switch for Alexa Presentation Language

e) Click on Displays on the left side menu below Interfaces

f) Select Start from scratch option

g) Click on mainTemplate on the lower left. This is the initial root element that needs to exist and cannot be deleted.

payload is how you will reference datasources that are passed in. We'll see this later in the tutorial.

Below is the current JSON representation:

{
  "type": "APL",
  "version": "1.0",
  "import": [],
  "resources": [],
  "styles": {},
  "layouts": {},
  "mainTemplate": {
      "parameters": [
          "payload"
      ],
      "items": []
  }
}

e) Add a Container by clicking the +

Below is the current JSON representation:

{
  "type": "APL",
  "version": "1.0",
  "import": [],
  "resources": [],
  "styles": {},
  "layouts": {},
  "mainTemplate": {
      "parameters": [
          "payload"
      ],
      "items": [
        {
          "type": "Container"
        }
      ]
  }
}

f) Add a Text element

  • Click on the Container in the Layout section and click + similarly to the previous step
  • Next to the Layout section you will find the properties that can be set on the Text element. In this case, we want to set text to Hello World. You will notice that Hello World now shows up in the display simulator.

Below is the current JSON representation:

{
  "type": "APL",
  "version": "1.0",
  "import": [],
  "resources": [],
  "styles": {},
  "layouts": {},
  "mainTemplate": {
      "parameters": [
          "payload"
      ],
      "items": [
        {
          "type": "Container",
          "items": [
            {
              "type": "Text",
              "text": "Hello World"
            }
          ]
        }
      ]
  }
}

Render the APL Document to Displays

a) Click the toggle switch in the lower panel (depending on your configuration) to change the layout interface from graphical to JSON. There you could find the APL Document JSON.

b) Copy the APL Document JSON. The document should be as follows:

{
  "type": "APL",
  "version": "1.0",
  "theme": "dark",
  "import": [],
  "resources": [],
  "styles": {},
  "layouts": {},
  "mainTemplate": {
      "parameters": [
        "payload"
      ],
      "items": [
        {
          "type": "Container",
          "item": [
            {
              "type": "Text",
              "text": "Hello World"
            }
          ]
        }
      ]
  }
}

c) Go to where index.js is located and in the same folder create a file named launch.json .

d) Paste the copied JSON generated from the APL Authoring Tool in launch.json.

e) Open index.js. Then in the LaunchIntentHandler we will add the directive to render our Hello World APL document.

Import the launch.json and in the handle function, let's add a directive for rendering the APL document by updating the return value to the following:

const launch = require('launch.json') // import the launch APL document

const LaunchRequestHandler = {
  canHandle(handlerInput) {
      return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    const speechText = "Hello World!";

    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .addDirective({ // Add APL RenderDocument directive
          type: 'Alexa.Presentation.APL.RenderDocument',
          version: '1.0',
          document: launch, // This references launch.json 
          datasources: {}
      })
  }
}

Passing Datasources to the APL Document

APL documents also support data sources, so let's refactor our code to pass in the text value instead of a hard-coded value. To do so we will need to update our Lambda code and APL document.

Updating the Node.js code

To update our Node.js code we need create a speechText object that is passed to datasources in the following this format:

datasources: {
  speechTextObject: {
    type: "Text",
    value: speechText
  }
}

Then update the handle function to the following:

handle(handlerInput) {
  const speechText = "Hello World!";

  // add directive to render APL document
  return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .addDirective({
          type: 'Alexa.Presentation.APL.RenderDocument',
          version: '1.0',
          document: launch, // This references launch.json
          datasources: { // This is where datasources could be added
            speechTextObject: { // Pass speechTextObject as a datasource
              type: "Text",
              value: speechText
            }
          }
      })
}

Updating the APL Document

To access the speechText that was passed into datasources from Node.js we need to reference payload from

"mainTemplate": {
    "parameters": [
      "payload"
    ]
}
Note: This variable name could be named something else and does not have to be payload, but it would have to be listed under parameters.

Let's update the hard-coded Hello World string with the speechText value we passed in from Node.js by updating the Containeritem with the following:

{
  "type": "Container",
  "item": {
    "type": "Text",
    "text": "${payload.speechTextObject.value}"
  }
}
The "${}" is how you do variable interpolation in APL, so in our case "${payload.speechTextObject.value}" is Hello World.
{
  "type": "Text",
  "value": "${speechTextObject.text}"
}

Putting everything together, the new APL document should look like this:

{
  "type": "APL",
  "version": "1.0",
  "theme": "dark",
  "import": [],
  "resources": [],
  "styles": {},
  "layouts": {},
  "mainTemplate": {
      "parameters": [
        "payload"
      ],
      "items": [
        {
          "type": "Container",
          "item": ["${payload.speechTextObject}"]
        }
      ]
  }
}

Verifying the Device Has a Display

Not all Alexa supported devices have a display, so we need to check if the device has a display and conditionally add an APL Render Document Skill Directive.

We will add the supportsAPL function to our backend right after our const Alexa = require('ask-sdk-core');:

const supportsAPL = function (handlerInput) { 
  const supportedInterfaces = handlerInput.requestEnvelope.context.System.device.supportedInterfaces;
  const aplInterface = supportedInterfaces['Alexa.Presentation.APL'];
  return aplInterface !== null && aplInterface !== undefined; 
}

Then we are going to refactor the code in the handle function inside handleHelloWorldIntent to the following:

handle(handlerInput) {
  const speechText = "Hello World!";

  // create a new response with the speech
  var response = handlerInput.responseBuilder
                    .speak(speechText)
                    .reprompt(speechText)

  // Check if the device supports APL before adding the directive to render the APL document
  if(supportsAPL(handlerInput)) {
    return response.addDirective({
        type: 'Alexa.Presentation.APL.RenderDocument',
        version: '1.0',
        document: launch,
        datasources: {
          speechTextObject: {
            type: "Text",
            value: speechText
          }
        }
    })
  }

  return response
}

Summary

Now we have a skill that integrates with multimodal displays using the Alexa Presentation Language (APL). This is a simple Hello World example that went through the basics mechanics of integrating APL with skills.

In this guide, we went through:

  • How to build APL documents with the Authoring Tool or raw JSON
  • Adding an APL document to our skill to display visuals
  • Passing data sources to the APL documents

What's next?

Currently it only renders Hello World, but there are more things that could be added to create voice rich interactive skills.

Resources

  • Alexa Presentation Language (APL)
  • Alexa Design Guide
  • Visual Experiences
  • APL Render Document Skill Directive
  • Nodejs Hello World Skill


For more tips on skill building follow me on Twitter @itspanw.
alexa skills kitalexaapltutorialalexa presentation language
10 |5000

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Article

Contributors

petertong contributed to this article