How to Create Lazy Loading Lists in Your Alexa Skill with the New Alexa Presentation Language Data Source Changes

Joe Muoio Apr 15, 2020
Share:
Build Node.js Optimize Multimodal Intermediate
Blog_Header_Post_Img

Lazy loading is a popular method of loading only the necessary data while in the background loading in more data that the customer might see if they interact with the UI. This is a technique frequently used on websites and in mobile apps. You can now build lazy loading list experiences using the latest Alexa Presentation Language (APL) version, 1.3. All Alexa multimodal devices supporting APL from the Echo Show to the Facebook Portal support the new 1.3 features including the ability to create lazy loading lists. This feature was made possible by changes to the data sources (including a new data source type, dynamicListIndex), a new request type, and new directives to create more dynamic lists. With these new features, you can build list experiences with large numbers of items to display to your customer that scroll “infinitely” or update the values of list items on device (including adding and removing items). This blog post will walk you through the changes to data sources in APL 1.3 and introduce you to the new directives to build lazy loading lists. If you prefer to look only at code, check out our new Alexa cookbook APL example, skill-demo-lazy-load-lists, which showcases lazy loading lists in a color selector Alexa skill.

Color picker gif
Understanding the Data Source Changes in APL 1.3

Before we get to building lazy loading lists, let’s talk about the changes to APL data sources. In APL, data sources allow you to bind data to parts of your APL document on the device. In older versions of APL, there is only the object data source and the untyped data source. The “object” data source is used with transformers such as the ssmlToSpeech transformer or the textToHint transformer. The untyped data source was used with everything else you wanted to bind to your APL document and was passed as a parameter to your document named payload. In previous versions of APL, the entirety of the APL data source object in the response was bound to this payload parameter and you would reference the data as payload.[YOUR_OBJECT_KEYS_HERE]. While this still works for backwards compatibility in your existing APL documents, in APL 1.3 and onward, you should now pass in the name of your top level variables in the data source to your APL document instead. For instance, a basic APL document in the past might be:

Copied to clipboard
{
  "type": "APL",
  "version": "1.2",
  "mainTemplate": {
    "parameters": [
        "payload"
    ],
    "item": {
      "type": "Text",
      "text": "${payload.displayText.helloText}"
    }
  }
}

With a data source of:

Copied to clipboard
{
    "displayText": {
        "helloText": "hello world!"
    }
}

Now, using that same data source, the better way to write the APL 1.3 document would be:

Copied to clipboard
{
  "type": "APL",
  "version": "1.3",
  "mainTemplate": {
    "parameters": [
        "displayText"
    ],
    "item": {
      "type": "Text",
      "text": "${displayText.helloText}"
    }
  }
}

Note the two changes (outside of the version change). The first change is the parameter name to the mainTemplate. We are now referencing our top level object and passing this in to the document. The second change is to the way we are referencing it. Since APL is no longer binding the entire data source payload to the “payload” parameter by default, we can remove all references to this. While the first APL document presented will still work in all versions of APL, you cannot use the new dynamicIndexList data source in conjunction with the “payload” parameter. If you use “payload” as the parameter name, it will bind the entire data source to this parameter which must be referenced in your document and will ignore the new functionality provided by the new data source type. If you use any other parameter names that are not “payload”, APL will look for those in the “datasources” section of the APL RenderDocument directive. Going forward and for creating new documents, use named data sources.

Getting Started with the DynamicIndexList

Version 1.3 of APL includes a new type of data source, dynamicIndexList. This gives you the tools needed to create dynamic data sources and lazy loading lists. Here is an example of the initial dynamicListIndex data source: 

Copied to clipboard
"datasources": {
    "colorDynamicSource": {
        "type": "dynamicIndexList",
        "listId": "DYNAMIC_INDEX_LIST_ID",
        "startIndex": 0,
        "minimumInclusiveIndex": 0,
        "maximumExclusiveIndex": 130,
        "items": [
           {
               "index": 0,
               "name": "Alice Blue",
               "value": "#F0F8FF"
           },
            ...,
            {
               "index": 9,
               "name": "Brown",
               "value": "#A52A2A"
           }
        ]
    }
}

This example represents a dynamicIndexList which defines a data source called “colorDynamicSource” and describes the first ten colors in a list of 130 colors, starting at index, 0. This will create a list which will scroll forwards and is bounded by the minimumInclusiveIndex and the maximumExclusiveIndex. If you want a backwards scrolling list, provide an index less than the startIndex. For instance, providing an index in the middle of the list (like 80), using the same bounding indices, would create a list which scrolls forwards and backwards. Once the bounding indices are requested by the device, the device will stop requesting for more data and not allow the user to scroll further. See the dynamicIndexList technical documentation to learn more about each of these fields. 

How to Create Lazy Loading Lists

Lazy loading lists also require the use of a dynamicIndexList data source. To create a lazy loading list, you must bind the dynamicIndexList to the “data” property of a pager or sequence (containers, while having a data property, are not supported for lazy loading lists, but are supported for other dynamic data source features). When needed, the Alexa-enabled multimodal device will send a new request for data using the loadIndexListDataRequest. For example:

Copied to clipboard
{
  "request": {
    "type": "Alexa.Presentation.APL.LoadIndexListData",
    "requestId": "amzn1.echo-api.request.1",
    "timestamp": "2020-03-26T19:47:02Z",
    "locale": "en-US",
    "token": "YOUR_RENDER_DOCUMENT_TOKEN",
    "correlationToken": "SYSTEM_GENERATED_TOKEN",
    "listId": "DYNAMIC_INDEX_LIST_ID",
    "startIndex": 20,
    "count": 10
  }
}

Notice a couple of things:

  • The device will ask for the starting index and the number of new items it needs to display. This is based on the amount of viewport space your list items take on the screen with the goal of limiting situations where the customer tries to scroll but the data has not loaded yet. These requests will all be bounded by the minimum and maximum indices of your list. The device will not request for items outside of the data source index bounds.
  • There is a token matching the document this request is originating from named “token”.
  • There is a separate token tied to the data source, named “listId”, which is tied to the identifier you provide for the list in the initial dynamicIndexList data source.
  • There is a third token, “correlation token”, which is for the device to correlation your response with its request.

The multitude of tokens mean you can have multiple lists in different documents and write handlers to respond accordingly. In your handler, you will need to respond with the new directive, SendIndexListData. This response JSON may look something like: 

Copied to clipboard
{
    "directives": [
        {
            "type": "Alexa.Presentation.APL.SendIndexListData",
            "token": "YOUR_RENDER_DOCUMENT_TOKEN",
            "correlationToken": "SYSTEM_GENERATED_TOKEN",
            "listId": "DYNAMIC_INDEX_LIST_ID",
            "startIndex": 20,
            "minimumInclusiveIndex": 0,
            "maximumExclusiveIndex": 130,
            "items": [
                {
                    "index": 70,
                    "name": "Lime Green",
                    "value": "#32CD32"
                },
                ...,
                {
                    "index": 78,
                    "name": "Medium Spring Green",
                    "value": "#00FA9A"
                },
                {
                    "index": 79,
                    "name": "Medium Turquoise",
                    "value": "#48D1CC"
                }
            ]
        }
    ]
}

The field “startIndex” should match the request and you should respond with the number of results that is requested by the device. The correlation token must match the request. The minimum and maximum indices are not necessary if they have been sent in a prior response. You can combine both this lazy loading list directive and the dynamic data sources UpdateIndexListData directives to build complex multimodal experiences. 

Try Out the Color Picker Example Skill Code

We have a Github sample in the Alexa-cookbook showcasing lazy loading lists. Here is a demo of the experience:

By touch, scroll through over 400 colors and select them by touch or voice. This will put that color at the top of the list and change the header color to the selected color. You can also change the main component in the APL document being used between a sequence component and a pager component by selecting “sequence” or “pager” in the header. This will send a new RenderDocument directive with the other document. All colors are served from the file, colors.json, but you can imagine a scenario where the data comes from a service call, instead. This is where lazy loading lists have a lot more value because your skill does not need to request all of the data at once, making this feature particularly helpful if you have paginated service calls. Service calls always come with some amount of latency. By making just one call for part of your data and letting the device make multiple future calls for further data, you can reduce latency and improve the customer experience.

This code sample also demonstrates a new capability, the APL error request. You can find the handler, called “APLRuntimeErrorHandler,” in the Node.js code . This is written exactly like we would write any request handler using the Alexa Skills Kit (ASK) SDK, only in this case, we are logging the error and returning an empty response. If the situation warrants it, you can return a response from here to course correct based on the error. You can find the entire set of error cases in the tech docs

The README.md will show you how to set up the skill. Feel free to test the skill out on your Alexa developer account and make changes to prototype your ideas. If you find any issues, you can reach out to me @JoeMoCode or open an issue on GitHub. I’d love to see what you create with this new data source and list directives!

Related Articles

Subscribe