アドベントカレンダーを作る 創刊～創刊号はSAP AppGyverアプリの作成手順付き～

※この記事は [SAP Advent Calendar 2021](https://adventar.org/calendars/6252) の12月22日分の記事として執筆しています。
# はじめに
私はSAP TechEdでSAP AppGyverの存在を知り、感動した。こんなにさっくりとアプリを作れるなんて…と。
そして今年のSAP Advent CalendarはSAP AppGyverをテーマに書くことに決めた。

ところで、"Advent Calendar"とは何だろうか。
>元々はクリスマスまでの日数をカウントダウンするために使われていたカレンダーで、12月1日からはじまり、25個ある「窓」を毎日1つずつ開けて中に入っている小さなお菓子やプレゼントを楽しむものです。 近年、このカレンダーにならい、インターネット上において定められたテーマに従い参加者が持ち回りで自身のブログやサイトに記事を投稿する企画が多く実施されています。

https://help.qiita.com/ja/articles/qiita-adcal-1

是非、カウントダウンで盛り上がりたいじゃないか・・・！
>窓を開くと写真やイラスト、詩や物語の一編、チョコレートなどのお菓子、小さな贈り物等が入っていることが多い

https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%89%E3%83%99%E3%83%B3%E3%83%88%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC

割と中身は何でもありじゃないか・・・！

そうだ、アドベントカレンダーAppをSAP AppGyverで作ろう。
# 構築物概要
窓を開くと写真やイラストが出てくるアドベントカレンダーAppを作成する。
アドベントカレンダーの中身を自分で仕込んで自分で開くことほど虚しいことはない（実はそれはそれで楽しいかもしれないが…）と思うので、
ある程度ランダム性を確保するために、検索エンジンで取得したデータを自動でカレンダーに埋め込む仕組みとしたい。
今回は画像検索、取得を行うサービスとしてMicrosoft Azureで提供されているサービスである"Bing Image Search"のAPIを使用する。

画面イメージは下記とする。
①でキーワードを入れ、②で日付を選択し、③で画像が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/6d644eea-a09f-4cdf-d2b1-8fa497f645a9.png)

# 前提条件
- SAP AppGyverのトライアルアカウントを作成済みであること。※SAP BTPのトライアル環境ではまだSAP AppGyverのサービスが使えない(2021/12/22現在)
- 検索エンジンより画像を取得できるAPIを用意していること。※何でもよい

# REST API作成編
今回はSAP BTPトライアル環境上にて、検索エンジンより画像を取得する機能を実装したREST APIをSpring Bootを用いて構築した。
先述の通り、Microsoft Azureの"Bing Image Search"を使用した。
本筋と逸れるため詳しい実装内容の説明は控えるが、構築方法については以下ブログを参照されたい。

https://docs.microsoft.com/ja-jp/azure/cognitive-services/bing-image-search/quickstarts/client-libraries?tabs=visualstudio&pivots=programming-language-java

https://qiita.com/kikutaro/items/4dd310cd0a660d57d839

※前者はMicrosoft Azure公式ドキュメント。公式で配布しているライブラリを使用すると認証エラーが発生することがあるらしく、後者の記事では公式ライブラリを使用しない方法を紹介している。

## API仕様
### リクエスト
|項目    |内容                               |
|------------|-----------------------------------|
|メソッド    |GET                                |
|パス        |/image/get                         |
|パラメーター|searchTerm（検索語句）             |
|            |count（検索結果の表示順の何番目か）|

※1日目は1番目、25日目は25番目という規則で画像をカレンダーに埋め込む。

### レスポンス
|Key         |内容                                |
|------------|------------------------------------|
|CONTENTS    |画像を表示するURL                   |
※今回使用するAPIでは、1リクエストに対して1レコード返却される。 
# プロジェクト作成編
まずはこちらからログインあるいはサインアップする。

https://www.appgyver.com/

CREATE NEWボタンを押下しプロジェクトを作成する。プロジェクト作成後は画像のように表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/8974b563-17a1-dfde-70f6-ce9a954868f8.png)

画面作成ページが現れる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/f0c7df72-a804-65ea-4e81-86392f624eb9.png)
# 画面作成編
左側に画面上に配置するオブジェクトの一覧がある。
以下のコンテンツから選んでドラッグアンドドロップすると、画面にオブジェクトを配置できるという寸法であり、直観的に非常に簡単に画面を作成できる。
オブジェクトの色、大きさ、埋め込むテキスト内容等は右側のpaneより設定できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/e57b58e5-5257-8731-01bc-e27be627be18.png)

## 画像検索ワード入力ページ
Input field、検索実行用のButtonを配置する。
タイトルは右側のプロパティで自由に編集できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/21e570b5-091b-ee11-87ea-7002aa00ecb8.png)

## アドベントカレンダーページ
LAYOUTグループの"Row"をドラッグ＆ドロップすると、任意の個数でオブジェクトを配置する際の仕切りを入れることができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/070c160b-ddad-3f3b-8185-ce6d7f7229a2.png)

今回は5×5個のレイアウトでButtonを配置していく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/e056fd67-82ee-e0c9-1a11-60dd87474f54.png)

## 画像表示ページ
画像表示用のオブジェクトであるImageと、アドベントカレンダーに戻るためのIconを配置する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/acd9a2b6-b3d8-1625-77a8-af76948e677d.png)


アイコンはPROPERTIESから選択することができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/665c9fdf-c3ae-05d6-7e76-7593724c798d.png)

## NAVIGATIONの定義
ページのヘッダやフッターに表示させる内容を定義する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/16979e67-9eaf-ef65-12ad-d35167431fee.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/4035e229-3760-fc07-1454-32e2a86d7f91.png)
# REST API呼び出し設定編
画面上部の"DATA"タブに入ると、DATA CONFIGURATORページが現れる。
ADD DATA RESOURCEより外部APIの呼び出し設定を実施することができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/b07662db-a11e-1e1a-4241-04966c8365c3.png)

作成したRESOURCEを押下すると設定画面が表示される。
左側paneにあるようにGET, POST, PUT, DELETEの各Httpメソッドが用意されている。
BASEでは、各メソッドで共通するURLパスをResource IDという識別子とともに登録する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/d25a7a6d-1ff4-961f-a926-4e0603011968.png)

今回はGETリクエストに対して1レコード返却するAPIを使うため、GET RECORD（GET）にて、先述したAPI仕様に基づき設定を行う。
パスはRelative path、リクエストパラメーターはQuery parameterにそれぞれ設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/fdc61f49-5c9e-2eb7-f4cd-283c6a4106eb.png)

Query parameterにはそれぞれ値を動的にセットしたいため、Is staticはオンとする。
また、それぞれ必須項目とするため、Is optionalはオフとする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/5eddf478-1f94-b90d-e331-8f9627122633.png)


上記設定を終えると、TESTタブにてAPIをテスト実行することができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/fb07ea94-6200-8933-8b9c-2a972849a77c.png)

実行結果を取得後、SET SCHEMA FROM RESPONSEを押下すると、取得項目をプロパティとして設定することができる。
プロパティはAPIで取得した値を画面上に出力する際に参照するために使用され、住所で言う所の番地のような役割を果たす。

Value typeで型定義を実施できるが、Value typeは取得値に合わせて自動で選択される。
今回定義した"CONTENTS"には画像を表示するURLが文字列として入って来るため、Value typeはWeb URL (http or https)として定義されている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/09db34cd-b0b9-67a7-caef-6e047277d7ce.png)
# ロジック作成編
ボタン押下時の挙動、値の受け渡しなど、処理ロジックを定義していく。
ロジックについても、ドラッグ＆ドロップで簡単に処理内容を定義することができる。
## 画像検索ワード入力ページ
Input fieldに入力した文字列は"searchTerm1"という変数に格納するよう設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/1afdd255-e5ad-419e-bcb6-ac92c2e3a6c1.png)

変数は画面上部の VIEW-VARIABLESトグルを切り替え、画面左側のpaneから種別を選んで作成する。

|VARIABLES    |内容                                |
|------------|-----------------------------------|
|APP VARIABLES|アプリ内で共通で使えるグローバル変数として定義する。read, writeが可能。|
|PAGE VARIABLES|特定ページ内のみで使えるローカル変数として定義する。read, writeが可能。|
|PAGE PARAMETERS|画面遷移時の値受け渡しに使用可能。read only. 使い勝手がわからず今回は使用していない。|
|DATA VARIABLES|DATA CONFIGURATORページで定義したAPIで値を取得し、格納できる。特定ページ内のみで使える。|
|TRANSLATION VARIABLES|ブラウザの言語に合わせてテキストの翻訳を行うために使う。Fioriでいうところのi18nに相当すると考えれば良さそう。|

"searchTerm1"については、後続の遷移ページにて使用したいため、APP VARIABLESとして作成した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/af0466af-cf2d-0a5a-cd2b-ace32365c21b.png)


次に、「決定」ボタン押下をトリガに、アドベントカレンダーページに遷移するようLOGICを定義する。
以下画像の矢印を押下するとLOGICを作成するためのpaneが表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/f72f16a0-80d9-eb29-a448-535cc87db196.png)

画面遷移を実現するためには、NAVIGATIONグループから"OPEN PAGE"をドラッグ＆ドロップする。
PROPERTIESにて遷移先のページ"Calendar page"を定義したうえで、デフォルトで設定されているEvent "Component tap"とNAVIGATION "OPEN PAGE"のコネクタをつなげると、「決定」ボタンのタップをトリガに"Calendat page"への遷移が実行される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/ea5f70fd-1c8d-2f9b-f76e-efd633873c04.png)

## アドベントカレンダーページ
各日付のボタンをタップした際の処理を定義していく。
まず、日付に該当する数字を文字列として保存し、遷移先の画像表示ページで使えるようにする。
"count1"は画面遷移先で使用できるよう、APP VARIABLESとして定義しておく。
VALIABLESグループから"Set app variable"をドラッグ＆ドロップし、Variable nameに"count1"を、Assigned valueに日付の数字を設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/4a73c4cb-9dcd-55d0-6bc7-02d300582c5f.png)


次に、画像表示ページへの画面遷移を設定する。Pageには遷移先のページ"content"を設定する。

最後に、コネクタはEVENT->VARIABLES->NAVIGATIONの順でつなげる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/a15f3fad-c381-0f8f-d87f-34d9783ee92f.png)


## 画像表示ページ
ページが表示されたタイミングで、REST APIで画像を取得し、画面上に出力する処理を作成する。
まず、REST APIをコールする処理を追加する。
DATAグループの"Get record"をドラッグ＆ドロップして、[REST API呼び出し設定編](#REST API呼び出し設定編)で定義したResource name "GetContent"を選択する。各パラメータには遷移元の処理で値を格納したsearchTerm1, count1をそれぞれ設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/2a12009d-b2cf-d929-79d5-1bf9de5b2812.png)


次に、APIコールのレスポンスから値を取得する処理を追加する。
VALIABLESグループから"Set deta variable"をドラッグ＆ドロップする。ここでは、DATA CONFIGURATIONで定義したプロパティである"CONTENTS"を参照して値を取得したいため、プロパティの参照元としてDATA VARIABLESを定義する必要がある。

ADD DATA VARIABLEから、DATAタブで作成したRESOURCEである"GetContent"を選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/79c11195-b013-bb77-8b14-d1669972d0f3.png)

以下の画面が表示される。
Data variable typeは"Single data record"を選択すること、デフォルトでLOGICに設定されている処理ボックスは削除すること、がポイントである。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/ddc1c0bf-8e03-2025-eb6a-b857fd02e3a7.png)
削除しない場合、"Get record"にて実行されるAPIコールが断続的に実行される。
logic flowが実行されている間は"Delay"に定義されている時間分の間隔を置いてAPIが繰り返し実行され続ける設定になっているようだ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/025706e3-a5c6-afda-16d7-639bf4548ffa.png)


"Set deta variable"の設定に戻る。Data variable nameには作成したDATA VARIABLES "GetContent1"を設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/65e2d2ae-57eb-8356-a390-68cc6bb5b6f7.png)

①INPUTSの"data"には、binding typeとして"object with properties"を選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/47e1e6bd-c8b1-9d27-7c4c-66237ac6190f.png)

②Object with propertiesとしてOutput value of another nodeを選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/7ea9f698-1943-6f42-9eb8-3d6130d0ceff.png)

③Select logic nodeで"Get record"を、Select node outputで"Record > CONTENTS"を選択しSAVEを押下することで、APIで取得した値を格納するためのプロパティを作成することができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/021e6da7-7959-9eb6-f524-9786f9068e34.png)


最後に、ページ上に画像を表示させる設定を行う。
ImageのPROPERTIESにある項目"Source"に、先ほど"Set deta variable"で定義したプロパティ"GetContent.CONTENTS"を指定する。
これにより、"GetContent.CONTENTS"に格納されたURLを読込み、画面上に画像を表示することができるようになった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/69a44007-ce4c-79bc-5fdc-290b4f079e61.png)
# ハマった点
アプリの動作確認に入る前に、構築途中でハマった点について述べておく。
## CORSエラー
DATA CONFIGURATORページにてREST APIの設定後にテスト実行した所、下記のエラーが発生。
SAP AppGyver上で構築したアプリとREST APIのドメインが異なるために、REST APIのサーバー側でアクセスがブロックされていた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/f322a4f2-14dc-b115-da16-b3814abaa4b0.png)

Fioriアプリ作成時は主に、destinationを設定してAPIを呼び出す形でCORSエラーを回避する方法をとっていたが、SAP AppGyverではどうするべきかわからず、若干ハマった。
結果として、REST APIのサーバー側で異なるドメインからのアクセスを許容するよう設定を行うことで解決することができた。

## レスポンス
こちらもDATA CONFIGURATORページにてREST APIの設定後にテスト実行した際に遭遇したエラー。
当初REST APIのレスポンスをJSON形式でなく、文字列単体を返すようににしていた所、テスト実行時に下記のエラー発生。
JSON形式でレスポンスを定義しなおした結果、正常にレスポンスを取得することができた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/d7253923-6732-63a0-ef37-8be819ee1b69.png)
# 動作確認
LAUNCHタブを開くと作成したアプリを起動する各種オプションが表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1944721/5e7d5650-b913-cfda-ef84-6cabe2618a12.png)

今回はMobile AppsのiOS AppとしてiPhone上でアプリを起動することにする。
その場合、事前にApp stroreにてSAP AppGyver PreviewというアプリをiPhoneにインストールしておく必要がある。

https://apps.apple.com/us/app/sap-appgyver-preview/id1585856868

SAP AppGyver Previewアプリ内でLAUNCHページに表示されているQRコードを読み込んでSAP AppGyverアカウントへログインし、iOS Appとして作成したアプリを起動することができる。

以下では、アプリ起動からアドベントカレンダーをめくるまでを紹介している。

https://twitter.com/nawataruna/status/1473553936330403844

# おわりに
今回、実はそんなに公式ドキュメントやチュートリアルを見ずに直観的な操作でアプリを作ることができていたりする。
なんとなくこれかな？というノリで動かしてみたらアプリができちゃった、というNo-code / Low-codeの真骨頂（？）を体験することができた。
画面の裏側の処理ロジックについては、初めて向き合ってみた時の戸惑い、物慣れなさは多少感じたものの、やはりコーディングスキルを求められないという点において優れていると思う。
何より、思いついたらすぐ作れる、という手軽さが良い。
無事にアドベントカレンダーAppを作り終えることができ、今年は良いクリスマスを迎えられそうである。

創刊号ではチェック処理、例外処理、イかしたページレイアウト等々はアプリに織り込まれておらず、最低限動くものが出来上がった、という感が否めなかったのも事実だ。
次号が発行されるのか、創刊した刹那、廃刊となってしまうのかは中の人のみぞ知るところだが、SAP AppGyverの研究も兼ねたアドベントカレンダーの更なる進化に期待したい。