Flutter × FirebaseでOGP付きURLを生成する
DEPARTURE Advent Calendar 2021 | 5日目
OGPとは
OGP とは Open Graph Protocol (オープン・グラフ・プロトコル)の略称です。
Facebook、TwitterなどのSNS上でシェアされた時やシェアされたい時に、ページのタイトル、URL、概要、画像(サムネイル)を正しく伝えるためにHTMLソースに記述するタグ情報です。
引用:https://seopack.jp/seo_articles/ogp.php
TwitterなどのSNSでよく見かけるこちらのことです。
アプリのことをSNSでシェアしてもらう時に、アプリ内の情報を反映させたOGP画像を作りたい場面があると思います。今回はこちらをFlutterとFirebaseの知識だけで作る方法を共有します。
開発環境
- VSCode v1.62.3
- Flutter v2.2.3
- Xcode v13.1
- Android Studio Arctic Fox|2020.3.1 Patch3
前提
- Flutterの新規プロジェクト作成済み
- Firebaseの新規プロジェクト作成済み
- FlutterプロジェクトとFirebaseプロジェクト接続済み
- Firebase Dynamic Links設定済み
- Firebase Storage設定済み
方針
- OGP画像をFlutterの
CustomPainter
で作成 - OGP画像をFirebaseStorageにUpload
- Uploadした画像のURL、リンクのタイトル、リンクの説明を設定したDynamic LinksをFlutter側で生成
実装
CustomPainter
で作成
1. OGP画像をFlutterのCustomPainterやCanvasについては以下の記事が分かりやすいです。
まずはCustomPainter
を継承したOgpPainter
を作成します。
※今回は考慮していませんが、描画する要素は横幅630pxに収めた方が良い場合もあります。Twitterは横幅1200pxまで表示してくれますが、アプリによってはOGP画像の表示領域が小さく真ん中630pxしか表示されない場合もあるからです。対応する場合はCustomPainterで要素の幅が真ん中630pxを超えないように実装してください。詳しくは以下をご覧ください。
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
class OgpPainter extends CustomPainter {
OgpPainter(this.logoImage);
final ui.Image logoImage;
void paint(Canvas canvas, Size size) {
const sideSpace = 200.0;
// ====================================
// 表示テキストの設定
// ====================================
final textSpan = TextSpan(
text: 'Flutter✌️',
style: TextStyle(
color: Colors.black.withOpacity(0.5),
fontSize: 160,
fontWeight: FontWeight.w400,
),
);
// ====================================
// painterの設定
// ====================================
final textPainter = TextPainter(
text: textSpan,
textDirection: ui.TextDirection.ltr,
);
// ====================================
// テキストを中心揃いにする
// ====================================
double centerTextPosY(double painterHeight) {
return (size.height - painterHeight) / 2;
}
textPainter.layout();
// ====================================
// 描画処理
// ====================================
// 背景を白色にする
final backgroundPaint = Paint()
..color = Color(0xffffffff)
..blendMode = BlendMode.color;
canvas.drawRect(
Rect.fromLTWH(
0,
0,
size.width,
size.height,
),
backgroundPaint,
);
// Flutterのロゴ画像を描画する
canvas.drawImage(
logoImage,
Offset(
sideSpace,
centerTextPosY(logoImage.height.toDouble()),
),
Paint(),
);
// "Flutter✌️"のテキストを描画
textPainter.paint(
canvas,
Offset(
sideSpace + logoImage.width + 48,
centerTextPosY(textPainter.height),
),
);
}
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
OgpPainter
の描画をByteData
に変換します。
///
/// OGP画像を生成
///
Future<ByteData?> _createOgpImage() async {
// OGP画像の基本サイズ
const imageWidth = 1200;
const imageHeight = 630;
ui.PictureRecorder recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
final logoImage = await assetImageToUiImage('assets/flutter_logo.png');
OgpPainter(logoImage).paint(
canvas,
Size(
imageWidth.toDouble(),
imageHeight.toDouble(),
),
);
final image = await recorder.endRecording().toImage(
imageWidth,
imageHeight,
);
final data = await image.toByteData(format: ui.ImageByteFormat.png);
return data;
}
///
/// AssetImage -> ui.Imageに変換
///
Future<ui.Image> assetImageToUiImage(String imageAssetPath) async {
Completer<ImageInfo> completer = Completer();
final img = AssetImage(imageAssetPath);
img
.resolve(ImageConfiguration())
.addListener(ImageStreamListener((ImageInfo info, bool _) {
completer.complete(info);
}));
ImageInfo imageInfo = await completer.future;
return imageInfo.image;
}
描画した結果が以下画像です(枠線内)。本家と見分けがつかないので【✌️ 】を付けました。
2. OGP画像をFirebaseStorageにUpload
FirebaseStorageに画像をUploadします。
///
/// FirebaseStorageへ画像をアップロード
///
Future<Uri?> _uploadImage(ByteData data) async {
final bytes = data.buffer.asUint8List();
final dateString = _formattedDate();
final ref = FirebaseStorage.instance.ref('ogp_images/$dateString.png');
try {
await ref.putData(
bytes,
SettableMetadata(
contentType: 'image/png',
),
);
return Uri.parse('https://storage.googleapis.com/${ref.bucket}/ogp_images/$dateString.png');
} on FirebaseException catch (e) {
print('OGP Image Upload Error = $e');
}
}
String _formattedDate() {
final dateTime = DateTime.now();
return '${DateFormat('yyyyMMddHHmmss').format(dateTime)}_${dateTime.microsecondsSinceEpoch}';
}
※注意点
Uploadした画像は一般公開にしないとOGPで表示されないので、下記記事を参考にバケットの公開設定を変更してください。
3. Uploadした画像のURL、リンクのタイトル、リンクの説明を設定したDynamic LinksをFlutter側で生成
///
/// DynamicLinksを生成
///
Future<Uri> _buildDynamicUrl(Uri imageUrl) async {
final DynamicLinkParameters parameters = DynamicLinkParameters(
uriPrefix: 'https://hoge.page.link', // Dynamic Linksで作成したURL接頭辞
link: Uri.parse('https://flutter.dev/'), // 遷移先URL
socialMetaTagParameters: SocialMetaTagParameters(
title: 'Flutter - Build apps for any screen',
description: 'Flutter transforms the app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase.',
imageUrl: imageUrl,
),
);
final dynamicUrl = await parameters.buildShortLink();
print('Dynamic Link Short Url = ${dynamicUrl.shortUrl}');
return dynamicUrl.shortUrl;
}
完成です。Twitterでリンクを入力すると以下のように表示されました。
全体コード
以上。
Discussion