※この記事はFlutter × FirebaseでOGP付きURLを生成するの記事をエクスポートしたものです。内容が古くなっている可能性があります。
OGPとは
OGP とは Open Graph Protocol (オープン・グラフ・プロトコル)の略称です。 Facebook、TwitterなどのSNS上でシェアされた時やシェアされたい時に、ページのタイトル、URL、概要、画像(サムネイル)を正しく伝えるためにHTMLソースに記述するタグ情報です。
引用:https://seopack.jp/seo_articles/ogp.php
アプリのことを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側で生成
実装
1. OGP画像をFlutterのCustomPainter
で作成
CustomPainterやCanvasについては以下の記事が分かりやすいです。 https://dev.classmethod.jp/articles/flutter_custom_paint/
まずはCustomPainter
を継承したOgpPainter
を作成します。
※今回は考慮していませんが、描画する要素は横幅630pxに収めた方が良い場合もあります。Twitterは横幅1200pxまで表示してくれますが、アプリによってはOGP画像の表示領域が小さく真ん中630pxしか表示されない場合もあるからです。対応する場合はCustomPainterで要素の幅が真ん中630pxを超えないように実装してください。詳しくは以下をご覧ください。
https://c3-d.jp/ogp-size-2020/
// ogp_painter.dart import 'dart:ui' as ui; import 'package:flutter/material.dart'; class OgpPainter extends CustomPainter { OgpPainter(this.logoImage); final ui.Image logoImage; @override 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), ), ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; } }
OgpPainter
の描画をByteData
に変換します。
// main.dart /// /// 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します。
// main.dart /// /// 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で表示されないので、下記記事を参考にバケットの公開設定を変更してください。 https://qiita.com/mako0715/items/a2049d31915f10f40681#%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%82%92%E5%85%AC%E9%96%8B%E3%81%99%E3%82%8B
3. Uploadした画像のURL、リンクのタイトル、リンクの説明を設定したDynamic LinksをFlutter側で生成
// main.dart /// /// 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でリンクを入力すると以下のように表示されました。
全体コード
https://github.com/taroooth/ogp_sample
以上。