CONTENT
ここから
前のページでは、月額費用を発生させずに、Mastodon・Bluesky・RSSから情報を収集する方針を決めました。
このページでは、アプリを動かすための開発環境を準備します。
今回使用するサービスは、次の3つです。
- Flutter:スマートフォンアプリの画面を作る
- Supabase:収集した投稿や記事を保存する
- GitHub Actions:一定間隔で情報収集処理を実行する
重要なのは、Flutterアプリにすべての処理を書かないことです。
SNSから情報を取得する処理と、利用者に情報を表示する処理を分離します。役割を明確に分けることで、APIキーの流出、取得回数の増加、重複データの登録などを防ぎやすくなります。
2.1 構成の全体像
今回作成するアプリでは、次の順番で情報を処理します。
Mastodon・Bluesky・RSS・YouTube
↓
GitHub Actionsの定期収集処理
↓
データ形式の統一・重複除去
↓
Supabase Database
↓
Flutterアプリ
それぞれの役割を詳しく見てみましょう。
情報源
最初に、公開されている情報を各サービスから取得します。
情報源
├─ Mastodon
│ └─ トレンドタグ・投稿・リンク
├─ Bluesky
│ └─ 指定キーワードに関する公開投稿
├─ RSS/Atom
│ └─ ニュース・ブログ・公式発表
└─ YouTube
└─ 指定キーワードに関する動画
Mastodon、Bluesky、RSSは、教材の必須取得対象です。
YouTubeはAPIキーと利用量の管理が必要になるため、任意機能として扱います。
GitHub Actions
GitHub Actionsでは、一定間隔ごとに収集プログラムを実行します。
例えば、3時間ごとに収集する場合は、1日に8回実行します。
GitHub Actions
├─ Mastodonを取得する
├─ Blueskyを取得する
├─ RSSを取得する
├─ 必要に応じてYouTubeを取得する
├─ データを共通形式へ変換する
├─ 重複を確認する
└─ Supabaseへ保存する
GitHub Actionsのスケジュール実行は、指定時刻ちょうどに始まることを保証する仕組みではありません。混雑時には実行が遅れる可能性があるため、「3時間ごとに必ず秒単位で実行する」のではなく、「おおむね3時間ごとに収集する」仕組みとして使用します。
Supabase
Supabaseには、収集した投稿や記事を保存します。
主に次のデータを管理します。
Supabase
├─ 収集した投稿・記事
├─ 情報源
├─ 投稿者
├─ 投稿日時
├─ 元ページのURL
├─ 反応数
├─ 話題度
├─ 取得日時
└─ 収集処理の実行結果
2026年7月24日時点のSupabase Freeでは、月額0ドルで500MBのデータベース、5GBの通常転送量、5GBのキャッシュ転送量、1GBのファイルストレージなどを利用できます。無料プロジェクトは2つまでで、1週間利用がない場合は一時停止されることがあります。
今回のMVPでは、投稿本文を大量に保存せず、タイトル、短い本文、公開日時、元URLなどを中心に保存します。
画像ファイルそのものも原則として保存せず、情報源が提供する画像URLだけを記録します。これにより、データベース容量と転送量を抑えます。
Flutterアプリ
Flutterアプリは、SNSへ直接アクセスするのではなく、Supabaseから整理済みの情報を取得します。
Flutterアプリ
├─ Supabaseからデータを取得する
├─ 話題度順に表示する
├─ 情報源で絞り込む
├─ キーワードで検索する
├─ 詳細を表示する
└─ 元投稿をブラウザで開く
Flutter側は「収集する場所」ではなく、「見る場所」として設計します。
2.2 Flutterプロジェクトを作成する
最初に、Flutterアプリを作成します。
本教材では、Flutterアプリと収集プログラムを同じGitHubリポジトリで管理します。ただし、ソースコードは別のディレクトリへ分けます。
最終的なリポジトリ名は、次のようにします。
sns-trend-collector
Flutterの動作確認
ターミナルを開き、Flutterが利用できる状態か確認します。
flutter doctor
環境に問題がなければ、Flutter SDK、Android開発環境、Xcode、接続端末などの確認結果が表示されます。
問題が表示された場合は、アプリを作成する前に解消します。
特にmacOSでiOSアプリを動かす場合は、次の準備が必要です。
-
Flutter SDK
-
Xcode
-
Xcode Command Line Tools
-
CocoaPods
-
iOS Simulatorまたは実機 Androidで確認する場合は、次の準備が必要です。
-
Android Studio
-
Android SDK
-
Android Emulatorまたは実機
プロジェクト用ディレクトリを作成する
作業する場所へ移動し、プロジェクト全体を格納するディレクトリを作成します。
mkdir sns-trend-collector
cd sns-trend-collector
続いて、appディレクトリ内へFlutterプロジェクトを作成します。
flutter create \
--org com.example \
--project-name sns_trend_collector_app \
app
-orgには、本番で使用する組織名やドメインを逆順にした値を指定します。 例えば、ドメインがexample.jpの場合は、次のようにします。
flutter create \
--org jp.example \
--project-name sns_trend_collector_app \
app
まだ正式な組織名が決まっていない場合は、仮の値でも構いません。ただし、iOSのBundle IdentifierやAndroidのApplication IDとして使われるため、公開前に正式な値を確認します。
Flutterでは、flutter createコマンドを使って新しいアプリのひな型を作成できます。
アプリを起動する
作成したFlutterプロジェクトへ移動します。
cd app
利用可能な端末を確認します。
flutter devices
続いて、初期アプリを起動します。
flutter run
カウンターアプリが表示されれば、Flutterプロジェクトの準備は完了です。
確認後、プロジェクトのルートへ戻ります。
cd ..
必要なパッケージを追加する
Flutterアプリで使用するパッケージを追加します。
cd app
flutter pub add supabase_flutter
flutter pub add flutter_riverpod
flutter pub add http
flutter pub add url_launcher
flutter pub add shared_preferences
cd ..
それぞれの役割は次のとおりです。
| パッケージ | 役割 |
|---|---|
supabase_flutter | Supabaseとの通信 |
flutter_riverpod | 画面の状態管理 |
http | HTTP通信の基本処理 |
url_launcher | 元投稿を外部ブラウザで開く |
shared_preferences | ブックマークなどを端末内へ保存する |
パッケージのバージョンを教材内へ固定せず、flutter pub addによって現在のFlutter環境と互換性のあるバージョンを追加します。
実際に採用されたバージョンは、次のファイルへ記録されます。
app/pubspec.yaml
app/pubspec.lock
pubspec.yamlには依存条件が記録され、pubspec.lockには実際に解決されたバージョンが記録されます。
2.3 Supabase Freeプロジェクトを作成する
次に、収集した情報を保存するSupabaseプロジェクトを作成します。
プロジェクトを作成する
Supabaseへログインし、新しいプロジェクトを作成します。
作成画面では、主に次の項目を設定します。
| 項目 | 設定例 |
|---|---|
| Organization | 自分のOrganization |
| Project name | sns-trend-collector |
| Database password | 自動生成した強力なパスワード |
| Region | 利用者に近いリージョン |
| Plan | Free |
データベースパスワードは、GitHubやソースコードへ保存しません。
パスワードマネージャーなど、安全に管理できる場所へ保存します。
リージョンを選択する
主な利用者が日本にいる場合は、日本から近いリージョンを選択します。
リージョンは、通信速度に影響する可能性があります。また、作成後に簡単には変更できない場合があるため、適当に選ばず、利用地域を確認してから決めます。
接続情報を確認する
プロジェクトの作成が完了したら、次の情報を確認します。
SUPABASE_URL
SUPABASE_PUBLISHABLE_KEY
SUPABASE_SECRET_KEY
新しいSupabaseプロジェクトでは、クライアント用にPublishable key、サーバー用にSecret keyを使用します。
古いプロジェクトでは、次の名前が表示される場合があります。
| 新しいキー | 従来のキー |
|---|---|
| Publishable key | anon key |
| Secret key | service_role key |
Supabaseは新しいPublishable keyとSecret keyへの移行を進めており、Flutterなどのクライアント側にはPublishable key、バックエンド処理にはSecret keyを使用する構成が推奨されています。
キーの役割を区別する
Publishable keyは、FlutterアプリからSupabaseへ接続するときに使用します。
Flutterアプリ
↓
SUPABASE_URL
SUPABASE_PUBLISHABLE_KEY
↓
Supabase
Flutterアプリは利用者の端末へ配布されるため、アプリ内の値を完全に隠すことはできません。
Publishable keyは、モバイルアプリやWebアプリなど、利用者から取得可能な公開環境で使うことを前提としたキーです。
ただし、Publishable keyを使っているだけでデータが安全になるわけではありません。
実際のアクセス制御は、SupabaseのRLSで行います。
Secret keyは、GitHub Actionsで動かす収集プログラムから使用します。
GitHub Actions
↓
SUPABASE_SECRET_KEY
↓
Supabaseへデータを登録
Secret keyは、Flutterアプリへ含めてはいけません。
Secret keyが流出すると、本来制限されているデータ操作まで許可される可能性があります。
無料枠で保存するための方針
Supabase Freeのデータベース上限は500MBです。上限を超えると、無料プロジェクトのデータベースが読み取り専用状態に移行する場合があります。
そのため、次の方針で保存量を抑えます。
- 投稿全文を必要以上に保存しない
- HTMLをそのまま保存しない
- 画像ファイルを複製しない
- 同じ投稿を重複保存しない
- 古い収集ログを定期的に削除する
- 削除済み投稿を無期限に保持しない
- 取得対象のキーワード数を増やしすぎない
MVPでは、1件あたりの保存データを数KB程度に抑える設計を目指します。
2.4 GitHubリポジトリを準備する
GitHubでは、次の2つを管理します。
- Flutterアプリと収集プログラムのソースコード
- 定期収集を実行するGitHub Actions
Gitリポジトリを初期化する
プロジェクトのルートで、Gitリポジトリを初期化します。
git init
初期ブランチ名をmainにします。
git branch -M main
ルート用の.gitignoreを作成する
Flutterプロジェクト内には.gitignoreが作成されていますが、プロジェクト全体で秘密情報を除外するため、ルートにも.gitignoreを作成します。
# 環境変数
.env
.env.*
!.env.example
# Dart
.dart_tool/
build/
# IDE
.idea/
.vscode/
*.iml
# macOS
.DS_Store
# ローカルログ
logs/
*.log
次のファイルはGitへ登録しません。
.env
collector/.env
APIキーが書かれた設定ファイル
データベースパスワード
Secret key
最初のコミットを作成する
git add .
git commit -m "chore: initialize Flutter project"
GitHubリポジトリを作成する
GitHub CLIを使用する場合は、次のコマンドで非公開リポジトリを作成できます。
gh repo create sns-trend-collector \
--private \
--source=. \
--remote=origin \
--push
GitHub CLIを使用しない場合は、GitHub上で空のリポジトリを作成し、画面に表示される手順に従ってリモートリポジトリを設定します。
公開・非公開のどちらを選ぶか
教材用であっても、最初は非公開リポジトリを推奨します。
理由は、次のとおりです。
- 作成途中の設定ミスを公開しにくい
- 誤って秘密情報を追加した場合の露出範囲を抑えられる
- アプリの仕様が確定する前に公開されない
- 収集対象や運用方針を整理してから公開できる
ただし、非公開リポジトリのGitHub Actionsには無料実行時間の上限があります。
2026年7月24日時点のGitHub Freeでは、GitHubホストランナーについて月2,000分の無料実行時間と、500MBのArtifactストレージが含まれています。
今回の収集処理は、短時間で終了する軽量な処理として作成します。処理時間は取得件数、APIの応答速度、再試行回数などによって変わるため、実装後にGitHub Actionsの実行履歴から確認します。
GitHub Actions用ディレクトリを作成する
プロジェクトのルートへ、次のディレクトリを作成します。
mkdir -p .github/workflows
定期収集用のファイルは、後のページで次の場所へ作成します。
.github/workflows/collect-trends.yml
この段階では、空のディレクトリをGitが管理しないため、まだワークフローファイルを作らなくても問題ありません。
2.5 APIキーを安全に管理する
今回のアプリでは、すべての設定値を同じ方法で管理してはいけません。
設定値には、公開されても問題のない値と、外部へ漏れてはいけない秘密情報があります。
設定値を分類する
| 設定値 | Flutter | GitHub Actions | 秘密情報 |
|---|---|---|---|
SUPABASE_URL | 使用する | 使用する | いいえ |
SUPABASE_PUBLISHABLE_KEY | 使用する | 原則不要 | いいえ |
SUPABASE_SECRET_KEY | 使用しない | 使用する | はい |
YOUTUBE_API_KEY | 使用しない | 使用する | はい |
| MastodonサーバーURL | 使用しない | 使用する | 通常はいいえ |
| Bluesky APIの接続先 | 使用しない | 使用する | 通常はいいえ |
| RSSフィード一覧 | 原則使用しない | 使用する | 通常はいいえ |
Flutter側の設定値
Flutterでは、次の2つを使用します。
SUPABASE_URL
SUPABASE_PUBLISHABLE_KEY
本教材では、ビルド時に--dart-defineで渡します。
flutter run \
--dart-define=SUPABASE_URL=設定値 \
--dart-define=SUPABASE_PUBLISHABLE_KEY=設定値
Dart側では、次のように読み込みます。
/// 役割: ビルド時に渡されたSupabase接続情報を保持する。
abstract final class AppEnvironment {
static const String supabaseUrl = String.fromEnvironment(
'SUPABASE_URL',
);
static const String supabasePublishableKey = String.fromEnvironment(
'SUPABASE_PUBLISHABLE_KEY',
);
}
ここで注意が必要です。
-dart-defineを使っても、Flutterアプリ内の値を秘密にできるわけではありません。ビルドされたアプリを解析すれば、値を取得される可能性があります。
そのため、Flutterへ渡してよいのは、公開環境での使用を前提とした値だけです。
Flutterへ入れてよい
├─ Supabase URL
└─ Supabase Publishable key
Flutterへ入れてはいけない
├─ Supabase Secret key
├─ 旧service_role key
├─ データベースパスワード
└─ 外部サービスの秘密トークン
GitHub Actions側の秘密情報
GitHub Actionsで使用する秘密情報は、Repository Secretsへ登録します。
登録する名前は、次のように統一します。
SUPABASE_URL
SUPABASE_SECRET_KEY
YOUTUBE_API_KEY
YouTubeを使用しない場合は、YOUTUBE_API_KEYを登録する必要はありません。
GitHubでは、リポジトリの設定画面からActions用のSecretを登録し、ワークフロー内で利用できます。
ワークフローでは、次のように環境変数へ渡します。
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_SECRET_KEY: ${{ secrets.SUPABASE_SECRET_KEY }}
YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY }}
Secretの中身をログへ出力してはいけません。
次のような処理は禁止します。
print(Environment.supabaseSecretKey);
エラー確認のためであっても、キーそのものは表示しません。
代わりに、設定されているかどうかだけを確認します。
/// 役割: 必須環境変数が設定されていることを確認する。
/// 入力: 環境変数名と値。
/// 出力: 値が存在する場合はその値。存在しない場合は例外。
String requireEnvironmentValue({
required String name,
required String? value,
}) {
if (value == null || value.trim().isEmpty) {
throw StateError('$name is not configured.');
}
return value;
}
ローカル開発用の.envを準備する
収集プログラムをローカルで実行するときは、collector/.envへ秘密情報を保存します。
SUPABASE_URL=
SUPABASE_SECRET_KEY=
YOUTUBE_API_KEY=
ただし、.envはGitへ登録しません。
代わりに、値を空にした.env.exampleだけを登録します。
SUPABASE_URL=
SUPABASE_SECRET_KEY=
YOUTUBE_API_KEY=
これにより、必要な環境変数名だけを他の開発者へ共有できます。
キーを誤って公開した場合
秘密情報をGitへコミットしてしまった場合は、ファイルを削除するだけでは不十分です。
Gitの履歴には、過去の内容が残るためです。
次の対応が必要です。
- 該当するキーを無効化する
- 新しいキーを発行する
- Git履歴から秘密情報を削除する
- GitHub Secretsを新しい値へ変更する
- ローカル環境の設定を更新する
- 不正利用がなかったかログを確認する
秘密情報は「見つかってから隠す」のではなく、最初からGitへ追加しないことが重要です。
2.6 FlutterからSNS APIを直接呼ばない理由
MastodonやBlueskyの公開APIは、技術的にはFlutterアプリから直接呼び出せる場合があります。
しかし、今回のアプリでは直接呼び出しません。
採用しない構成
Flutter
├─ Mastodon API
├─ Bluesky API
├─ RSS
└─ YouTube API
代わりに、GitHub Actionsで収集した結果をSupabaseへ保存し、FlutterはSupabaseだけを参照します。
採用する構成
SNS・RSS
↓
GitHub Actions
↓
Supabase
↓
Flutter
理由1:利用者の人数だけAPIリクエストが増える
Flutterから直接取得すると、アプリを開く利用者ごとにAPIリクエストが発生します。
利用者A → SNS APIへアクセス
利用者B → SNS APIへアクセス
利用者C → SNS APIへアクセス
利用者D → SNS APIへアクセス
100人がアプリを開けば、同じ情報を取得するために似たリクエストが何度も実行されます。
GitHub Actionsでまとめて取得する場合は、1回取得した情報を複数の利用者へ配信できます。
GitHub Actions → SNS APIへ1回アクセス
↓
Supabase
↙ ↓ ↘
利用者A 利用者B 利用者C
理由2:秘密情報をアプリへ含めずに済む
YouTube APIキーやSupabase Secret keyをFlutterへ埋め込むと、アプリから取り出される可能性があります。
バックエンド側に集約すれば、秘密情報をGitHub Secretsで管理できます。
理由3:同じ投稿の重複登録を防ぎやすい
複数の利用者が同時にデータを取得すると、同じ投稿が何度も保存される可能性があります。
収集処理を一か所へ集約すれば、保存前に重複を確認できます。
投稿を取得
↓
情報源と投稿IDを確認
↓
すでに存在する?
├─ はい → 更新または処理終了
└─ いいえ → 新規登録
理由4:データ形式を統一できる
各サービスのレスポンス形式は異なります。
Mastodonの投稿形式
Blueskyの投稿形式
RSSの記事形式
YouTubeの動画形式
Flutterで個別に処理すると、画面側のコードが複雑になります。
GitHub Actions側で共通形式へ変換してから保存すれば、Flutterはすべて同じTrendItemとして扱えます。
Mastodon ─┐
Bluesky ─┤
RSS ├─→ TrendItem → Supabase
YouTube ─┘
理由5:アプリを開いていなくても収集できる
Flutterから取得する構成では、基本的に利用者がアプリを開いたタイミングでしか情報を収集できません。
今回の目的は、一定間隔で情報を蓄積し、反応数の変化やキーワードの増加を調べることです。
そのため、利用者の操作とは独立して収集処理を動かす必要があります。
理由6:エラー処理を一か所へまとめられる
SNS APIへの接続では、次のような問題が発生します。
- 通信タイムアウト
- 一時的なサーバーエラー
- APIの利用制限
- 不正なJSON
- 削除済み投稿
- 空の検索結果
- RSSの形式違い
- 必須項目の欠損
GitHub Actions側へ処理をまとめることで、再試行、ログ保存、エラー通知などを一か所で管理できます。
理由7:収集処理を変更してもアプリ更新が不要になる
SNS APIの仕様変更に対応するたびにFlutterアプリを変更すると、利用者へ新しいバージョンを配布しなければなりません。
バックエンド側でデータを共通形式に保てば、収集処理だけを修正できます。
SNS APIの仕様変更
↓
collectorのみ修正
↓
Supabaseの形式は維持
↓
Flutterアプリは変更不要
ただし、Flutterから外部APIを直接呼ぶこと自体が、常に間違いというわけではありません。
天気情報の表示など、利用者の操作に応じてその場で取得する必要があるアプリでは、直接通信が適する場合もあります。
今回のように、定期収集、重複除去、話題度計算を行うアプリでは、バックエンド側へ集約する構成が適しています。
2.7 ディレクトリ構成を設計する
プロジェクト全体は、次の構成にします。
sns-trend-collector/
├─ app/
│ ├─ android/
│ ├─ ios/
│ ├─ lib/
│ │ ├─ app/
│ │ │ ├─ app.dart
│ │ │ └─ router.dart
│ │ │
│ │ ├─ core/
│ │ │ ├─ config/
│ │ │ ├─ errors/
│ │ │ ├─ network/
│ │ │ └─ theme/
│ │ │
│ │ ├─ features/
│ │ │ ├─ trends/
│ │ │ │ ├─ data/
│ │ │ │ ├─ domain/
│ │ │ │ └─ presentation/
│ │ │ │
│ │ │ ├─ search/
│ │ │ │ ├─ data/
│ │ │ │ ├─ domain/
│ │ │ │ └─ presentation/
│ │ │ │
│ │ │ └─ bookmarks/
│ │ │ ├─ data/
│ │ │ ├─ domain/
│ │ │ └─ presentation/
│ │ │
│ │ └─ main.dart
│ │
│ ├─ test/
│ ├─ pubspec.yaml
│ └─ pubspec.lock
│
├─ collector/
│ ├─ bin/
│ │ └─ collect.dart
│ │
│ ├─ lib/
│ │ ├─ src/
│ │ │ ├─ config/
│ │ │ ├─ clients/
│ │ │ │ ├─ mastodon/
│ │ │ │ ├─ bluesky/
│ │ │ │ ├─ rss/
│ │ │ │ └─ youtube/
│ │ │ │
│ │ │ ├─ models/
│ │ │ ├─ repositories/
│ │ │ ├─ services/
│ │ │ └─ utils/
│ │ │
│ │ └─ collector.dart
│ │
│ ├─ test/
│ ├─ .env.example
│ ├─ pubspec.yaml
│ └─ pubspec.lock
│
├─ supabase/
│ └─ migrations/
│
├─ .github/
│ └─ workflows/
│ └─ collect-trends.yml
│
├─ .gitignore
└─ README.md
appディレクトリ
appには、Flutterアプリのコードを配置します。
app
├─ 画面表示
├─ 状態管理
├─ Supabaseからの読み込み
├─ 検索
├─ 絞り込み
├─ ブックマーク
└─ 外部ブラウザの起動
SNS APIからの収集処理やSecret keyを使う処理は置きません。
collectorディレクトリ
collectorには、SNSやRSSから情報を収集するDartプログラムを配置します。
collector
├─ Mastodonへの接続
├─ Blueskyへの接続
├─ RSSの解析
├─ YouTubeへの接続
├─ データ形式の統一
├─ 重複確認
├─ 話題度の計算
└─ Supabaseへの保存
Flutterとは別のDartコンソールプロジェクトとして作成します。
プロジェクトのルートで、次のコマンドを実行します。
dart create -t console-simple collector
作成後、収集処理に必要なパッケージを追加します。
cd collector
dart pub add http
dart pub add supabase
dart pub add xml
dart pub add dotenv
cd ..
それぞれの役割は次のとおりです。
| パッケージ | 役割 |
|---|---|
http | SNS APIやRSSへのHTTP通信 |
supabase | Supabaseへの保存 |
xml | RSS・AtomのXML解析 |
dotenv | ローカル環境変数の読み込み |
収集処理はFlutterのUI機能を必要としないため、supabase_flutterではなく、Dart向けのsupabaseパッケージを使用します。
clientsディレクトリ
SNSごとの通信処理を分けます。
clients/
├─ mastodon/
│ ├─ mastodon_client.dart
│ └─ mastodon_response.dart
├─ bluesky/
│ ├─ bluesky_client.dart
│ └─ bluesky_response.dart
├─ rss/
│ ├─ rss_client.dart
│ └─ rss_response.dart
└─ youtube/
├─ youtube_client.dart
└─ youtube_response.dart
APIレスポンスをそのままアプリ全体で使用せず、各クライアント内で安全に変換します。
modelsディレクトリ
アプリ内で共通して扱うデータモデルを配置します。
models/
├─ trend_item.dart
├─ trend_source.dart
├─ collection_result.dart
└─ collection_error.dart
例えば、Mastodon、Bluesky、RSSのデータは、最終的にすべてTrendItemへ変換します。
MastodonStatus ─┐
BlueskyPost ─┤
RssItem ─┼─→ TrendItem
YouTubeVideo ─┘
repositoriesディレクトリ
外部サービスやデータベースとのやり取りをまとめます。
repositories/
├─ trend_repository.dart
└─ collection_log_repository.dart
TrendRepositoryは、次の処理を担当します。
- 投稿が登録済みか確認する
- 新しい投稿を保存する
- 既存投稿の反応数を更新する
- 古い投稿を削除する
- 収集結果を記録する
servicesディレクトリ
複数の処理を組み合わせるビジネスロジックを配置します。
services/
├─ collection_service.dart
├─ normalization_service.dart
├─ duplicate_detection_service.dart
└─ trend_score_service.dart
役割は次のように分けます。
| サービス | 役割 |
|---|---|
CollectionService | 各情報源の収集処理を実行する |
NormalizationService | SNSごとのデータを共通形式へ変換する |
DuplicateDetectionService | 同じ投稿や記事を判定する |
TrendScoreService | 話題度を計算する |
API通信、データ変換、重複判定、話題度計算を一つのファイルへ書かないことが重要です。
supabase/migrationsディレクトリ
Supabaseのテーブル定義やRLSを、SQLファイルとして管理します。
supabase/migrations/
├─ 202607240001_create_trend_items.sql
├─ 202607240002_create_collection_logs.sql
└─ 202607240003_add_rls_policies.sql
Supabaseの管理画面だけでテーブルを作成すると、どのような変更を行ったか追跡しにくくなります。
マイグレーションファイルとして残すことで、データベース構造の変更履歴をGitで管理できます。
.github/workflowsディレクトリ
GitHub Actionsの設定ファイルを配置します。
.github/workflows/
└─ collect-trends.yml
このファイルには、後のページで次の設定を記述します。
- 実行間隔
- Dartのセットアップ
- 依存パッケージのインストール
- Secretの受け渡し
- 収集プログラムの実行
- 失敗時の終了処理
プロジェクト作成後の構成を確認する
準備が完了した段階では、次の状態になっています。
sns-trend-collector/
├─ app/
├─ collector/
├─ supabase/
│ └─ migrations/
├─ .github/
│ └─ workflows/
├─ .gitignore
└─ README.md
変更内容をGitへ保存します。
git add .
git commit -m "chore: prepare app and collector structure"
git push
まとめ
このページでは、無料でSNSトレンド収集アプリを動かすための基盤を準備しました。
全体の役割分担は、次のとおりです。
Flutter
└─ 整理された情報を利用者へ表示する
GitHub Actions
└─ 一定間隔で収集プログラムを実行する
Collector
├─ SNS・RSSから情報を取得する
├─ データ形式を統一する
├─ 重複を除外する
└─ Supabaseへ保存する
Supabase
├─ 収集した情報を保存する
├─ Flutterへデータを配信する
└─ RLSでアクセスを制御する
APIキーについては、次の区別が重要です。
Flutterで使用する
├─ SUPABASE_URL
└─ SUPABASE_PUBLISHABLE_KEY
GitHub Actionsだけで使用する
├─ SUPABASE_SECRET_KEY
└─ YOUTUBE_API_KEY
FlutterアプリからSNS APIを直接呼ばず、GitHub Actionsで情報収集を一元管理します。
これにより、次の問題を防ぎやすくなります。
- 利用者数に応じてAPIリクエストが増える
- 秘密情報がFlutterアプリから流出する
- 同じ投稿が重複登録される
- SNSごとの処理が画面コードへ混在する
- アプリを開いていない間は収集できない
- API変更のたびにアプリ更新が必要になる
次のページでは、Mastodon、Bluesky、RSSから取得した異なるデータを、共通のTrendItemモデルとして扱う仕組みを作成します。