How to create, install and maintain APL Widgets

Gaetano Ursomanno Oct 11, 2023
Share:
Alexa Skills
Blog_Header_Post_Img

 

This blog explains how you can create, install, and maintain APL widgets for your Alexa skills.

Prerequisites:


If you have built APL documents before, you should be familiar with the concept of datasources. These allow developers to bind external data to an APL document without the need to change its structure.

This feature is usually referred to as data-binding, which is explained in more detail in this blog post and this video.

While widgets still support datasources, they can also read from the datastore, which represents a local storage area accessible by your widget. You are responsible for keeping the datastore up to date if your widget needs to show different information over time, and you will learn how to do this as well.

For reference, this is the JSON representation of a widget supporting the datastore:

 

Copied to clipboard
{
    "document": {
        "type": "APL",
        "version": "2022.2",
        "extensions": [
            {
                "name": "DataStore",
                "uri": "alexaext:datastore:10"
            }
        ],
        "settings": {
            "DataStore": {
                "dataBindings": [
                    {
                        "namespace": "Widget",
                        "key": "WidgetKey",
                        "dataBindingName": "WidgetDataStoreData",
                        "dataType": "object"
                    }
                ]
            }
        },
        "import": [
            {
                "name": "alexa-layouts",
                "version": "1.6.0"
            }
        ],
        "mainTemplate": {
            "parameters": [
                "WidgetDefaultData"
            ],
            "items": {
                "type": "Container",
                "width": "100vw",
                "height": "100vh",
                "id": "rootContainer",
                "direction": "row",
                "item": [
                    {
                        "type": "Frame",
                        "backgroundColor": "${WidgetDataStoreData.frameColor ? WidgetDataStoreData.frameColor : WidgetDefaultData.frameColor}",
                        "position": "absolute",
                        "width": "100vw",
                        "height": "100vh"
                    },
                    {
                        "type": "Container",
                        "width": "100vw",
                        "height": "100vh",
                        "items": [
                            {
                                "text": "${WidgetDataStoreData.text ? WidgetDataStoreData.text : WidgetDefaultData.text}",
                                "textAlign": "center",
                                "textAlignVertical": "center",
                                "type": "Text",
                                "width": "100%",
                                "height": "100%"
                            }
                        ]
                    }
                ]
            }
        }
    },
    "datasources": {
        "WidgetDefaultData": {
            "frameColor": "green",
            "text": "Hello from datasources"
        }
    }
}

 

As you can see, the widget’s document block has the extensions and settings objects with datastore information, which is something you don’t usually see on regular APL documents. 

The datasources part instead holds the default values for the background color and text that the widget will render as soon as it’s installed.

With this configuration the widget’s default text will be “Hello from datasources”, but it will change to whatever you include into the datastore update payload required to keep the widget up to date (more on that later).

The key to making this work relies on the expression currently set for the “text” property of the Text component (reported below):

Copied to clipboard
"text":"${WidgetDataStoreData.text ? WidgetDataStoreData.text : WidgetDataSourcesData.text}"

 

This means that the widget will read the information from the datastore if the latter is populated. If the datastore is empty, the widget will just render the text present in the datasources (as in, “Hello from datasources”).

The same mechanism applies to the Frame component as well: it will read the backgroundColor from the datastore if populated, or from the datasources if the datastore is empty.

Now that you know how the widget can retrieve data from different areas, let’s move on and associate it to an actual skill.

Associating a Widget to a Skill

Since an APL widget needs be associated to a skill, you must create one choosing one the following methods (skip if you have a skill already):

  1. Alexa Developer Console
  2. ASK-CLI

Once your skill is created, make sure to enable the “Alexa Presentation Language” interface from the “Interfaces” section of the “Build” tab, then save and re-build the skill package by pressing the “Build skill” button.

You are now ready to start designing your widget. Open the “Build” tab of the developer console again, click on “Multimodal Responses”, then “Widget” and eventually “Create Widget”.

You will see different templates; just press on “Blank Document” to start from scratch and the APL Authoring Tool for widgets will open. 

From there, paste the content of “document” and “datasources” object from the paragraph above respectively into the “APL” and “DATA” sections. Alternatively, save this JSON file and import it using the “Upload” button.

The rendered widget should look like this:

 

The widget is expectedly showing “Hello from datasources” with a green background, and this information is retrieved from the datasources. Press the save button on the upper-right corner to associate this widget to your skill. 

Note that each structural change made to the widget requires you to re-build the skill, so please do that as soon as the widget is saved, otherwise its installation (covered in the next paragraph) will either fail or install an older version of it.

Installing the widget

To install the widget, just select the target device on the bottom drop-down menu, click “Install” and then “Send to Device”. 

In a few minutes you should see that the widget has reached your widget gallery. Great job, your widget prototype is now installed in your device!

Note that your backend will receive a Alexa.DataStore.PackageManager.UsagesInstalled request for each time a customer installs your widget. 

Updating the widget’s datastore

The widget’s datastore can be updated from the Authoring Tool by pressing the “Update Data Store” button, which requires the following payload 

Copied to clipboard
[
  {
    "type": "PUT_OBJECT",
    "namespace": "Widget",
    "key": "WidgetKey",
    "content": {
        "frameColor": "blue",
        "text": "Hello from datastore"
    }
  }
]

 

Note how the values of namespace and key are respectively matching the ones present in the settings section of the widget’s document.

This will change the background to blue and the text to “Hello from datastore”, but will only target the device selected in the drop-down menu of the developer console. This mechanism is meant to be used for the development stage only.

In a production environment you should update the datastore using the Data Store REST API, which requires authentication against the Alexa API Endpoint and allows two different kind of targets:

“DEVICE”:
Targets one or multiple device IDs (up to 20 for each API call)

“USER”:
Targets all of the devices associated to a given userid

How to use the Data Store REST API:

1) Retrieve an access token from the Alexa API Endpoint:

Copied to clipboard
const axios = require('axios');

// Get those from the "Alexa Skill Messaging" section in Build --> Tools --> Permissions
const skillClientID = ""
const skillClientSecret = ""

// Change the endpoint based on your region
// NA: https://api.amazonalexa.com
// EU: https://api.eu.amazonalexa.com
// FE: https://api.fe.amazonalexa.com

function getAccessToken() {
    let config = {
        method: "post",
        url: "https://api.amazon.com/auth/o2/token",
        timeout: 3000,
        headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            charset: "utf-8",
        },
        params: {
            grant_type: "client_credentials",
            client_id: skillClientID,
            client_secret: skillClientSecret,
            scope: "alexa::datastore"
        }
    };

    return axios(config)
        .then(function (response) {
            return response.data;
        })
        .catch(function (error) {
            console.log(error)
        });
}

const tokenResponse = await getAccessToken();

 

2) POST the PUT_OBJECT payload to the Alexa API Endpoint:

Copied to clipboard
let dataStoreUpdateCommands = [
  {
    type: "PUT_OBJECT",
    namespace: "Widget",
    key: "WidgetKey",
    content: {
      frameColor: "blue",
      text: "Hello from datastore"
    },
  },
];

let dataStoreUserTarget = {
  type: "USER",
  id: handlerInput.requestEnvelope.context.System.user.userId,
};

let dataStoreDeviceTarget = {
  type: "DEVICE",
  id: handlerInput.requestEnvelope.context.System.device.deviceId,
};


async function updateDatastore(token, commands, target) {
    const config = {
        method: "post",
        url: `https://api.amazonalexa.com/v1/datastore/commands`,
        headers: {
            "Content-Type": "application/json",
        Authorization: `${token.token_type} ${token.access_token}`,
        },
        data: {
            commands: commands,
            target: target,
        },
  };

  return axios(config)
        .then(function (response) {
        console.log(JSON.stringify(response.data));
        return response.data;
        })
        .catch(function (error) {
        console.log(error);
        });
}

await updateDatastore(tokenResponse, dataStoreUpdateCommands, dataStoreUserTarget); 

 

Note that the snippet above is using the dataStoreUserTarget object to target a unique userId. You can use dataStoreDeviceTarget to target a specific deviceId instead.

Removing the widget’s datastore

To remove data from the datastore, follow the same steps from the “Updating the widget’s datastore“ section, but use the REMOVE_OBJECT type instead. Here is an example payload:

Copied to clipboard
[
  {
    "type": "REMOVE_OBJECT",
    "namespace": "Widget",
    "key": "WidgetKey"
  }
]

 

Like regular APL, widgets support the TouchWrapper component.

When a user taps on a Widget, the latter can generate two different kinds of interactions:

“STANDARD“:
The skill backend will receive an Alexa.Presentation.APL.UserEvent request, and can respond with regular skill responses, including an output speech.

“INLINE“:
The skill backend will receive a Alexa.Presentation.APL.UserEvent request, which can only be responded with a background action, such as querying a database or updating the datastore. 

The type of interaction (“STANDARD“ vs “INLINE“) is declared in a flag inside the SendEvent APL command. See interactionMode object in the example below:

Copied to clipboard
  {
    "type": "TouchWrapper",
    "id": "myTouchWrapper",
    "spacing": "@spacingSmall",
    "alignSelf": "center",
    "onPress": [
      {
        "type": "SendEvent",
        "arguments": [
          "Send this data to the skill"
        ],
        "flags": {
            "interactionMode": "INLINE" // or STANDARD
          },
        "components": [
          "myText"
        ]
      }
    ],
    "item": {
      "type": "Text",
      "id": "myText",
      "text": "Tap to send a UserEvent to the skill."
    }
  }

 

Wrapping up

APL Widgets are an awesome way to enhance your skill and create additional entry points to your experience. Now that you have all that you need to create, install, and maintain an APL widget for your skill, I would suggest having a look at the additional resources below to boost your APL knowledge and create even richer visuals both for your widgets and your skills.

Additional resources

Add Visuals and Audio to Your Skill
About Widgets and Alexa Presentation Language (APL)
APL Widgets Reference

Recommended Reading

New developer tools to build LLM-powered experiences with Alexa
How to easily get started with WebRTC
Earn money with an Alexa Skill

Subscribe