[Alexa]APLのカスタムパッケージの作成・インポートをする

# APLのimportを使い、複数画面間の部品共通化をしたい
Alexaスキル開発をしていると、APLで複数画面間で共通な画面要素を使いたいという場面が出てきます。
例えば、一つのAPLドキュメントにて独自のボタンデザインをLayoutsで定義しているが、これをスキル内の全画面共通で使用したい、というような具合です。
カスタムパッケージを作成し、APLドキュメントの"import"を使ってインポートすることでこれを実現することができます。
以下、APLでのカスタムパッケージ作成・インポートを実現するために実際にやってみた内容の記録です。

# やること
以下を実施します。

1. カスタムパッケージの作成、配置
2. APLドキュメントファイルの作成
3. APLデータソースファイルの作成
4. ハンドラにてAPL描写処理を追加

## 1. カスタムパッケージの作成、配置
カスタムパッケージを作成します。
公式のリファレンスを見てもフォーマットに関する具体的な記載がないので書き方が不明でしたが、以下の記事に記載されているJSONの形式でうまくいきました。
(記事内ではこれではうまくいかなかった、という趣旨で記載されていますが…)
参考：[Importing custom package to APL view - Forums](https://forums.developer.amazon.com/questions/197932/importing-custom-package-to-apl-view.html)

今回の例では、上記のURLで提示されているものをベースに、"TestPackage.json"という名称で以下のようなJSONを作ってみました。

``` TestPackage.json
{
    "type": "APL",
    "version": "1.0.0",
    "layouts": {
        "Test": {
            "parameters": [
                {
                    "name":"paramText",
                    "type":"string"
                }
            ],
            "item": {
                "type": "Container",
                "width": "100%",
                "height": "100%",
                "alignItems": "center",
                "justifyContent": "center",
                "position": "absolute",
                "items": [
                    {
                        "type": "Text",
                        "text": "${paramText}",
                        "color":"#aa3d2c",
                        "width": "${viewport.shape != 'round' ? '16vw' : '28vw'}",
                        "height": "${viewport.shape != 'round' ? '24vh' : '28vw'}"
                    }
                ]
            }
        }
    }
}
```
このパッケージを一応解説すると、以下のような感じです。

- "Test"というレイアウトを定義している。
- "Test"は"paramText"というパラメータをもっている。
- "paramText"に指定した文字列は、"Test"内のTextコンポーネントにセットされ、画面上に表示される。

このファイルをどこか公開しているサイトに置いておきます。
なお、配置するサイトにてCORSをサポートしている必要があるとのことです。
参考：[APLドキュメント（1.3） | Alexa Skills Kit](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-document.html#import)

このCORS対応ですが、S3に配置する場合は以下の感じで対応します。

- バケット のアクセス権限 > CORSの設定 と進んで、CORS構成エディターに以下を入力して保存する。

``` xml
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
</CORSRule>
</CORSConfiguration>
```
参考：[Alexa Presentation Language（APL）に対応するようスキルを設定する | Alexa Skills Kit](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-support-for-your-skill.html#support-cors)


これでカスタムパッケージの準備は完了です。

## 2. APLドキュメントファイルの作成

APLのドキュメント(テンプレート)部分では、カスタムパッケージのインポートをすれば、パッケージ内で定義している要素が利用できるようになります。

今回は、APLドキュメントとして以下のようなjsonファイルを作ってみました。

``` PackageTestTemplate.json
{
    "type": "APL",
    "version": "1.3",
    "settings": {},
    "theme": "dark",
    "import": [
        {
            "name": "test-package",
            "source": "【カスタムパッケージのURLをここに記入】",
            "version": "1.0.0"
        }
    ],
    "resources": [],
    "styles": {},
    "onMount": [],
    "graphics": {},
    "commands": {},
    "layouts": {},
    "mainTemplate": {
        "parameters": [
            "aplData"
        ],
        "items": [
            {
                "type": "Container",
                "width": "100vw",
                "height": "100vh",
                "items": [
                    {
                        "type": "Test",
                        "paramText": "${aplData.testText}"
                    }
                ]
            }
        ]
    }
}
 ```
"import"内にカスタムパッケージを定義しています。

name：何かと一致させる、のような決まりはなさそう
source：カスタムパッケージ(今回でいうと、TestPackage.json)のURLをセット
version：なんでも良さそうだが、一応カスタムパッケージ側で定義しているversionと一致させた

また、mainTemplate内にて、カスタムパッケージで定義している"Test"をセットしています。
パラメータになる"paramText"はデータソースファイル側にセットされた値を参照する方式にしてみました。

## 3. APLデータソースファイルの作成
続いて、APLのデータソース部にあたるファイルを準備します。
上記の通り、ドキュメントにてデータソース内の値を参照するようになっているので、そこをしっかり定義してあげます。
今回は以下としました。

``` PackageTestDataSource.json
{
    "aplData":{
        "testText":"ダミー(ハンドラに記載したプログラムでここの値を上書きする)"
    }
}
```

## 4. ハンドラにてAPL描写処理を追加
材料が揃いましたので、あとはスキル側で画面描写する処理を記載します。
以下の感じで記載できます。
(※ソースの一部のみ抜粋)

``` index.js 
// 対話モデルにてPackageTestIntentを作成しておく
// PackageTestIntent発話時にこのハンドラに入る想定
const PackageTestIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'PackageTestIntent';
    },
    handle(handlerInput){
        let st = 'パッケージインポートのテストです。';

        // APLドキュメントとデータソースを取得
        const PackageTestTemplate = require('./apl/PackageTestTemplate.json');
        var PackageTestDataSource = require('./apl/PackageTestDataSource.json');
        
        PackageTestDataSource.aplData.testText = '画面表示したい文字列';

        // 画面作成 ※実際はAPL対応デバイスかどうかの確認が必要
        handlerInput.responseBuilder.addDirective({
            type: 'Alexa.Presentation.APL.RenderDocument',
            version: '1.0',
            document: PackageTestTemplate,
            datasources: PackageTestDataSource
        });
        
        return handlerInput.responseBuilder
            .speak(st)
            .reprompt(st)
            .getResponse();
    }
}
```

# 実行してみる
実際に実行してみると、カスタムパッケージに記載されたレイアウトが読み込まれ、画面に表示されることが確認できます。
ソース内でセットした文字列が画面に反映されています。
![スクリーンショット 2020-06-20 15.58.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354808/f512843d-0796-7c60-e61f-83ac3457099f.png)

# まとめ
カスタムパッケージを使うことで、APL資産を再利用可能な部品として扱うことができるようになるので、みなさんぜひご活用ください。

# 備考
今回のカスタムパッケージではLayouts(レイアウト)だけを使っていますが、公式のドキュメント見る感じだと、リソースやスタイルもimportの対象になっているようです(※実際に使えるかどうかは未検証)。
6/21追記：styles(スタイル)もインポートできました。リソースは試していないですが、同じ要領なのでできそうな気がします。

# 参考URL
[APLドキュメント（1.3） | Alexa Skills Kit](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-document.html#import)
[APLパッケージ | Alexa Skills Kit](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-package.html)
[Alexa Presentation Language（APL）に対応するようスキルを設定する | Alexa Skills Kit](https://developer.amazon.com/ja-JP/docs/alexa/alexa-presentation-language/apl-support-for-your-skill.html#support-cors)
[Importing custom package to APL view - Forums](https://forums.developer.amazon.com/questions/197932/importing-custom-package-to-apl-view.html)