FlutterとSpearlyでHTMLなし・サーバーレスのお問い合わせフォームを作る
はじめに
自社サイトに埋め込んでいるお問い合わせフォームですが、iframe経由で運用するのがだいぶ辛くなってきました。
Flutter Webで作るのをやめれば問題は一気に解決するのですが、それを辞めるわけにはいかない(後述)ので、今回はFlutter Webの標準機能でお問い合わせフォームを作り変えることにしました。
今回も、Spearlyを利用するのでHTMLレス・サーバーレスになります。
この際なので、 microCMSでの実装も考えましたが、
- フォーム作成がGoogleフォームやformrun、Spearlyと比べて直感的でない
- メール通知を自前の実装する必要がある
という点から諦めました。逆にこの点が気にならない人はmicroCMSでフォームを作成しても良いかも知れません。
流れはざっと以下の通りです。
- Spearly CMSでフォームを作成する
- フォームに合わせてWidgetを生成する
- ボタン押下時にSpearlyのAPI経由でフォームを送信する
Spearly CMSでフォームを作成
公式サイトに従ってフォームを作ります。
今回は「API利用」で始めましょう。
フォームに合わせてWidgetを生成する
作成したフォームに合わせてWidgetを生成していきます。
「埋め込みプレビュー」の画面からフォームをHTMLで生成したときの画面が表示されるので、参考にすると良いでしょう。
Flutterで組み上げるので、HTMLを書くより早く組み上がることでしょう。
とりあえずは簡素なもので作りますが、希望に応じてバリデーションを設置してあげるのもよいでしょう。
今では TextFormField のサンプル集を出している人がたくさんいるので、ぜひ活用させていただきましょう!
ここに関してはFlutterでUIを組み上げているだけなので特別な知識はいりません。
下記のサンプルはいつものカウンターアプリを書きかえています。
長くなるので、シンプルな構成で書いてます。
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
final formKey = GlobalKey<FormState>();
final nameFieldController = TextEditingController();
final emailFieldController = TextEditingController();
final contentFieldController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Form(
key: formKey,
autovalidateMode: AutovalidateMode.disabled,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
labelText: 'お名前',
),
controller: nameFieldController,
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'メールアドレス',
),
controller: emailFieldController,
),
TextFormField(
minLines: 5,
maxLines: 10,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(
labelText: 'お問い合わせ内容',
),
controller: contentFieldController,
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: ElevatedButton(
onPressed: () async {
// ボタン内のアクションは後で指定します
},
child: const Padding(
padding: EdgeInsets.all(10),
child: Text(
'送信',
),
),
),
),
],
),
),
);
}
}
ボタン押下時にSpearlyのAPI経由でフォームを送信する
APIにPOSTリクエストを送る必要があるので、httpパッケージを導入します。
Spearlyの「埋め込み方法」からformのidを取得して、 form_version_id
にセットします。
この form_version_id
はフォームの内容を変更するたびに更新され、それぞれのバージョンごとにデータを送信できるので、うっかり古いフォーム(チェックボックスを新しく追加した、入力項目名を変更した等)を使ったままのページがあっても問題なく動作するようになっているみたいです。
final body = {
// Spearlyのページからform_version_idを取得します
'form_version_id': 0,
'fields': {
'name': nameFieldController.text,
'email': emailFieldController.text,
'content': contentFieldController.text
}
};
ドキュメントに沿ってAPIキーの生成を行います。
final body = {
// Spearlyのページからform_version_idを取得します
'form_version_id': 0,
'fields': {
'name': nameFieldController.text,
'email': emailFieldController.text,
'content': contentFieldController.text
}
};
final jsonBody = jsonEncode(body);
final res = await http.post(
Uri.parse('https://API.spearly.com/form_answers'),
headers: {
// SpearlyのページからAPIキーを取得します
'Authorization': 'Bearer APIkey',
'Accept': 'application/vnd.spearly.v2+json',
'Content-Type': 'application/json'
},
body: jsonBody);
APIキーをアプリ内に載せたくない人は、flutter_dotenvパッケージを使うと良いでしょう。
そうして書いていったコードが次のようになります。
今回も長くなるので、ボタンの中身のみ書いています。
// 略
ElevatedButton(
onPressed: () async {
// APIでのフォーム送信 ここから
final body = {
// Spearlyのページからform_version_idを取得します
'form_version_id': 0,
'fields': {
'name': nameFieldController.text,
'email': emailFieldController.text,
'content': contentFieldController.text
}
};
final jsonBody = jsonEncode(body);
final res = await http.post(
Uri.parse('https://API.spearly.com/form_answers'),
headers: {
// SpearlyのページからAPIキーを取得します
'Authorization': 'Bearer APIkey',
'Accept': 'application/vnd.spearly.v2+json',
'Content-Type': 'application/json'
},
body: jsonBody);
// APIでのフォーム送信 ここまで
if (res.statusCode != 201) {
return;
}
await showDialog<void>(
context: context,
builder: (_) {
return AlertDialog(
title: const Text(
'送信成功',
),
content: const Text(
'お問い合わせありがとうございます',
),
actions: <Widget>[
ElevatedButton(
child: const Text(
'OK',
),
onPressed: () => Navigator.pop(context),
),
],
);
},
);
},
child: const Padding(
padding: EdgeInsets.all(10),
child: Text(
'送信',
),
),
),
// 略
送信成功後のダイアログを出してますが、ここも好きに組み替えて構いません。
完成品
そうして組み上げて行ったコードと実際に動かしているサイトが以下のとおりです。
サンプルコード
利用しているパッケージはiOS・Androidでも使えるものなので、アプリに組み込むことも可能です。
というか、Flutterはアプリ用のフレームワークなので、そちらに使うのが最適でしょう。
サンプルは StatefulWidget を使ってますが、当然Riverpodなどの状態管理ライブラリも使えます。
APIにSpearlyを使っているだけなので。
作ったサイト
こっちはちゃんとバリデーションとかも入ってます。
(ただイタズラの問い合わせはやめてね)
制作の背景
そもそもなぜiframeから書き換えたのか、他の候補は考えなかったのかという背景は以下のとおりです。
- Flutterメインで仕事をしているので、サイトもFlutter Web製のものにしたかった
- Flutter上でiframe経由でフォームを生成するとパフォーマンスが悪い
- Cloud Functionsを書くのもしんどい
Flutterメインで仕事をしているので、サイトもFlutter Web製のものにしたかった
サイトを作るだけなら、WordPressやWix、素のHTML/CSS、Nuxtなんかで作成するのが良いのは十分承知の上です。
HTMLで作ればWidgetのようなコンテンツを自動生成してくれるJSライブラリが使えますし、WordPressならプラグインを追加すれば簡単にお問い合わせフォームが作れます。
しかし、Flutterのお仕事を受けることが多い都合上、Flutterでできることを実験的に色々見せたいところがあったので、Flutter Webで製作しました。
SEOもそんなに強くないので、よほどの理由がない限りはFlutterで静的サイトはまだ作らないほうが良いです。
Flutter上でiframe経由でフォームを生成するとパフォーマンスが悪い
そもそもFlutter Web上でiframeを使うケースが稀なためかあまりパフォーマンスがよくありません。
iframeを置いた位置にスクロールが若干引っかかる感じが出てきてしまいます。
Cloud Functionsを書くのもしんどい
今回のサイトはFirebase Hostingでホスティングしているため、Cloud Functionsも簡単に(実質無料で)利用できます。
Firestoreを使えば比較的簡単にフォームのAPIを作成できるでしょう。
ただし、チョットめんどくさいなという気持ちが勝ったので、この案はなしになりました。
おまけ(通知について)
お問い合わせフォームから連絡があった際の通知について、
- メールはSpearly内で設定したら自動で送ってくれるのでSpearly内で設定
- Slack通知はWebhookが使えるので自分で作成
するようにしました
最後に
以上のような流れでFlutterのWidget製のサーバーレスのお問い合わせフォームを作ることができました。
今回は取り上げてないですが、チェックボックスやラジオボタン、ファイルアップロードにも対応しているようなので、Widgetを組んでSpearlyとの連携の設定をするだけで、簡単にお問い合わせフォームが作れそうです。
個人的には
- フォームの更新に応じてWidgetを自動生成したい
- 開発のヒアリングシートを別ページとしてFlutter Web製にしたい
という気持ちがあるので、気分が乗ったらやります。
Discussion