How to Build a Multimodal Alexa Skill

Build Your First Visuals with the APL Authoring Tool

Table of Contents

In this section, we are going to learn about the authoring tool and use this to create an APL LaunchRequest response document for our Cake Walk skill. By the end of this section, you’ll simply have a screen that says, "Welcome to Cake Walk!," with a small image of a cake. To make this screen, you’ll learn about the following:

  • APL image and text components
  • Styles
  • Data binding basics
  • Responsive documents

Let’s get started.

We suggest using Firefox or Chrome for this course. Safari is not supported in the authoring tool.

1. Hello Cake Walk

A. Click here to go to the authoring tool. Save this link as a bookmark, since we will be referencing it throughout the course.

B. You will be greeted with a request to see the new experience. Click Yes.

The new experience will allow you to save your APL Documents to your skill.

C. Find the skill you are using in the dropdown and click Select.

 

D. Click the Create Template. Now, you’ll see a number of different templates

E. We are going to Start from scratch. This will give us a basic APL document without any components added to the mainTemplate.

F. In the authoring tool, make sure GUI is selected on the left side. This will give us the graphical representation of the structure of the document (under Layouts) as well as some text boxes where properties can be filled in. On the right side you will see the components you can drag and drop into place. Now drag in a text component. You’ll see something like this.

 

G. Let’s add some text, "Hello Cake Walk!". Click on the text component under the layouts section.

H. Scroll down the middle section until you find the text property of the text component. Change this message to Hello Cake Walk!.

I. Above this, find the fontSize property and change this to 72dp. We want to increase the text size to account for our customers which are looking at the device from far away. Now the text is outside of the blue bounding box…​Uh oh! Because the text is bound to such a small rendering box, it no longer displays the text properly with such a large font. Let’s fix this.

J. Scroll down to the Width section and find the width property of our Text component. Delete the value in the width text box, so it will default to auto.

K. Do the same with height under the Height section. Auto is a better setting to use since it will automatically scale the size of the bounding box for the text to match the content. This will help us later if we choose to modify the font size in the future as we did above.

You should now see something like this.

Congratulations! You have a functioning hello cakewalk document. You can see the document output under the APL tab in the sidebar.

For reference here is the JSON that you should have.

{
    "type": "APL",
    "version": "1.1",
    "settings": {},
    "theme": "dark",
    "import": [],
    "resources": [],
    "styles": {},
    "onMount": [],
    "graphics": {},
    "commands": {},
    "layouts": {},
    "mainTemplate": {
        "parameters": [
            "payload"
        ],
        "items": [
            {
                "type": "Container",
                "items": [
                    {
                        "type": "Text",
                        "paddingTop": "12dp",
                        "paddingBottom": "12dp",
                        "fontSize": "72dp",
                        "text": "Hello Cake Walk!"
                    }
                ],
                "height": "100%",
                "width": "100%"
            }
        ]

    }
}

We created a text component and then modified some properties on it using the GUI. To see the full set of text component properties, check out the text component docs. If you looked at that page, you will see that paddingTop and paddingBottom are missing. That is because all components also inherit properties from their parents. Here is the full list of style properties that all components share.

The authoring tool also created a container for us. Containers are components that house other components in an alignment of "column" or "row". Since containers are also components, they have the basic component properties. Containers are essential for building responsive APL documents. Complex documents will use a mix of row and column containers to properly lay out components in a way which scales nicely across devices.

2. Hello Cake Walk With Style

Some of the component properties can be defined using styles. Styles are a named set of properties that can be reused across components.

Not all properties can be styled on all components. Here is the full list of style-able properties for each component type.

A. Click the styles sidebar. You’ll see an empty set of parenthesis. Now, replace the empty set with this:

{
    "bigText": {
        "values": [
            {
                "fontSize": "72dp"
            }
        ]
    }
}

B. Click back into the APL tab in the sidebar and you will see your document has been updated with the styles added to the styles section.

C. Now, let’s modify the Text item to delete the fontSize property and add the following:

"style": "bigText"

You will see that the properties are still observed since this is now pulling from the style you defined. You can test this by changing the fontSize property in the style block. Your APL code will now look like this:

Let’s take this a step further and center our text using styles.

D. In the styles section, let’s add the textAlign property and set this to centered.

"textAlign": "center"

This will leave you with a style blob looking like:

{
    "bigText": {
        "values": [
            {
                "fontSize": "72dp",
                "textAlign": "center"
            }
        ]
    }
}

Even though you have not changed the actual text component, since it is using the bigText style, this is now applied to the Text component.

3. Data binding

Did you notice the Data button? This simulates the data that can be a part of the Alexa.Presentation.APL.RenderDocument directive which is what you send from your skill backend to render the document. We’ll come back to that later, but first, let’s look at how to build our document with data sources.

To reference data in a datasource, you’ll need to refer to it in the payload using payload.[YourDefinedObject]. This is called "payload" by default when in the authoring tool since it is listed as a parameter to the "mainTemplate". If you want a different name for the payload, you will have to change this parameter, but for now, leave it as is. We’ll add another Text component which will use a datasource and the style we defined. To reference the data, you will write an expression like, ${YourDefinedObject}.

Now that we have some familiarity with text components, let’s modify our APL JSON.

A. Add the following inside the container’s items array, underneath the existing text object:

{
    "type": "Text",
    "style": "bigText",
    "text": "${payload.text.middle}"
},
{
    "type": "Text",
    "style": "bigText",
    "text": "${payload.text.end}"
}

B. While we’re at it, let’s change the text data in our very first text component to ${payload.text.start}. Wait a minute…​ Where did that go? The text disappeared because we have no data in the data source we are referencing. Let’s fix this using that Data tab.

C. After clicking the Data button, you’ll see an empty dataset {}. We’ll need to add data which follows the structure we set. So we have a "text" object with "start", "middle", and "end" fields.

D. Add the following to the Data section of the authoring tool:

{
    "text": {
        "start": "Welcome",
        "middle": "to",
        "end": "Cake Walk!"
    }
}

The data payload represents variable data in the document. We are going to reuse this layout later to render similarly structured text with new data. This technique will allow you to more easily localize this skill since all of the localization logic can live in the backend. In addition, we are going to leverage this functionality to reuse our APL document. You’ll see the following:

Now, we have a set of reusable styles across this APL document, and we learned about making a screen using data binding. Let’s add an image of a birthday cake.

4. Adding a birthday cake

We’ll need to add an image component and use databinding. Image components use a URL to the resource that is storing the image. However, image is a primitive component. To scale the image across all of the viewport sizes would take a lot of effort and multiple image resolutions since it does not auto scale. Instead, use the AlexaImage responsive component so we can use a single image that will scale across all device resolutions.

To use the AlexaImage component, we’ll need to add an import. Imports allow you to reference layouts, styles, and resources defined in other packages. We are going to use a standard package called alexa-layouts. The import looks like this:

{
    "name": "alexa-layouts",
    "version": "1.1.0"
}

Add this import object to your import list in your APL document import section. Afterwards, this will look like:

{
    "type": "APL",
    "version": "1.1",
    "settings": {},
    "theme": "dark",
    "import": [
        {
            "name": "alexa-layouts",
            "version": "1.1.0"
        }
    ],
 ...<Omitted_rest_of_doc>
}

Alexa layouts is an important package for creating responsive layouts. The AlexaImage component has many parameters, most of which are optional.

B. Add the following image block inside of a new container underneath the last text component. This new block should be nested within the existing Container, so be sure to put it in the same "items" array as your text components.

{
    "type": "AlexaImage",
    "alignSelf": "center",
    "imageSource": "${payload.assets.cake}",
    "imageRoundedCorner": false,
    "imageScale": "best-fill",
    "imageHeight":"40vh",
    "imageAspectRatio": "square",
    "imageBlurredBackground": false
}

Let’s break this down:

  • For the fields we are using in the AlexaImage, imageSource is important since it specifies the URL where the image is hosted.
  • We want to give it the standard landscape aspect ratio since we’ll want to maintain our image resolution.
  • When the image scales, it will use the best-fit strategy.
  • To control the size, we are using the imageHeight property and set it to 40% of the viewport height.

To learn more about each of these, check out the parameters in the tech doc. If you look at the tech docs, you’ll notice no reference to alignSelf. This property exists and works because the component is a child component of a container. AlignSelf will override the container alignment for that child, only. There are some other properties that are added since this is a child of a container, too. This relies on a new "assets.cake" object to be added to the data section. The new data section will look like:

{
    "text": {
        "start": "Welcome",
        "middle": "to",
        "end": "Cake Walk!"
    },
    "assets": {
        "cake":"https://github.com/alexa/skill-sample-nodejs-first-apl-skill/blob/master/modules/assets/alexaCake_960x960.png?raw=true"
    }
}

Update your data blob with this new data. Then you’ll see:

How does it look? Delicious!? This is starting to look more like a birthday-themed skill. Let’s make this work for the other viewport profiles, too.

5. Making a responsive document

Below the simulator screen that we have been viewing our changes in, you’ll see some Echo devices with screens. We have been using the "Medium Hub" device (which is the Echo Show screen parameters) for now, but there are many other supported devices. Now, let’s try out our document on other screens.

A. Click the various symbols on the top and take note of any issues you find.

The simulator device types

  • Small Hub [Round] (480x480)
  • Small Hub [Landscape] (960x480)
  • Medium Hub (1024x600)
  • Large Hub (1280x800)
  • Extra Large TV (1920x1080)
  • Add Custom Device (any x any)

The last option gives you the ability to create whichever screen resolution you want to simulate the device rendering.

WARNING: spoiler below

Figure 1. Well, that doesn’t look quite right…​

B. Our wording is cut off on the Small Hub (Round) device screen. Let’s fix this using the when property. This property allows for boolean evaluation. If true, it will show a component and its children, but if false, it will not. In addition to when, we will be using Resources from the alexa-layouts import. Resources are simply named constants which are referenced with @<Resource_name>. This time, we will use the alexa-layouts package’s definitions of constants representing the above device types and viewport profiles. It allows you to create statements with predefined viewport-specific constants such as:

${@viewportProfile == @hubLandscapeLarge}

rather than

${viewport.width == "1280dp"}

There is no difference between these statements for an Echo Show 2 device request. But, let’s consider there is a new device with a 1300dp wide screen. Should we add another statement to this conditional? What about for a third device in a similar class? By using the Amazon defined resources, we will have better scaling APL documents without even knowing all the possible screen size permutations. This is because @hubLandscapeLarge represents screens between 1280 and 1920 wide, so it encompasses more devices of that class. Even though it is in the same class of device, since the screen does not match exactly the width we are checking, it will not render anything.

C. Since our document looks good on all devices except for the round small hub device, let’s add in a new set of components for that one. Click on the Small Round Hub icon.

D. Since a false evaluation will lead to no children components displaying, let’s add the following statement at the top of our first container.

"when":"${@viewportProfile != @hubRoundSmall}"

E. You should see a black screen! Check it out on the rectangular screens and your components will render. Since we omitted the @hubRoundSmall class from this container and its children, we will need to make a new container which will render when we are on a @hubRoundSmall device.

F. Now under that first container, duplicate the container and child Text components and add it to the items list of the mainTemplate. You’ll want to add the inverse of the statement above to this block:

"when":"${@viewportProfile == @hubRoundSmall}"

G. Now, we’ll fix the display. This can be achieved just by adding some padding to the top of the first text component.

"paddingTop": "75dp",

H. Next, remove all of the other padding values in that those text boxes.

I. Then, remove the cake image. Now your display should look properly on each of the device types. Check your work across the different classes to make sure it looks right to you.

J. We will use this JSON in the next section.. Save your APL document as launchDocument.json.

As an aside, there are a number of different ways we could have fixed this document for the small round hub profile. We could just keep the image and drop the text, or move the image to the background of the small round hub. In terms of structure, we could keep everything in one container and conditionally add the padding and hide the image to provide the same experience. The benefit to this technical approach is that we will not get newly added components by default in the future. Which also means as we iterate and change the rectangular hubs, we will not be modifying the structure of our small round hub screens. Since the screen is fundamentally different from others especially in our design, we forked it. Feel free to take a different approach for other skills if it suits your designs better!

The final APL Document JSON for reference:

{
    "type": "APL",
    "version": "1.1",
    "settings": {},
    "theme": "dark",
    "import": [
        {
            "name": "alexa-layouts",
            "version": "1.1.0"
        }
    ],
    "resources": [],
    "styles": {
        "bigText": {
            "values": [
                {
                    "fontSize": "72dp",
                    "textAlign": "center"
                }
            ]
        }
    },
    "onMount": [],
    "graphics": {},
    "commands": {},
    "layouts": {},
    "mainTemplate": {
        "parameters": [
            "payload"
        ],
        "items": [
            {
                "type": "Container",
                "when":"${@viewportProfile != @hubRoundSmall}",
                "items": [
                    {
                        "type": "Text",
                        "style": "bigText",
                        "paddingTop": "12dp",
                        "paddingBottom": "12dp",
                        "text": "${payload.text.start}"
                    },
                    {
                        "type": "Text",
                        "style": "bigText",
                        "paddingTop": "12dp",
                        "paddingBottom": "12dp",
                        "text": "${payload.text.middle}"
                    },
                    {
                        "type": "Text",
                        "style": "bigText",
                        "paddingTop": "12dp",
                        "paddingBottom": "12dp",
                        "text": "${payload.text.end}"
                    },
                    {
                        "type": "AlexaImage",
                        "alignSelf": "center",
                        "imageSource": "${payload.assets.cake}",
                        "imageRoundedCorner": false,
                        "imageScale": "best-fill",
                        "imageHeight":"40vh",
                        "imageAspectRatio": "square",
                        "imageBlurredBackground": false
                    }
                ],
                "height": "100%",
                "width": "100%"
            },
            {
                "type": "Container",
                "when":"${@viewportProfile == @hubRoundSmall}",
                "items": [
                    {
                        "type": "Text",
                        "style": "bigText",
                        "paddingTop": "75dp",
                        "text": "${payload.text.start}"
                    },
                    {
                        "type": "Text",
                        "style": "bigText",
                        "text": "${payload.text.middle}"
                    },
                    {
                        "type": "Text",
                        "style": "bigText",
                        "text": "${payload.text.end}"
                    }
                ],
                "height": "100%",
                "width": "100%"
            }
        ]
    }
}

Let’s put this document to use in the next section.

Complete code in Github