Flutter + Adjust SDK:広告流入計測の仕組みと実装ガイド
広告経由でアプリをインストールしたユーザーがどこから来たのかを計測したい。そんなときに使うのがAdjustのようなMMP(Mobile Measurement Partner)だ。
この記事では、Adjustの仕組みと、Flutterアプリへの導入方法を解説する。
想定環境: WebViewを内包したFlutterアプリ。ネイティブ部分はFlutterで薄くラップし、主要な画面・機能(登録、課金など)はWebViewで表示するタイプのアプリを前提とする。
Adjustとは
Adjustは、モバイルアプリの広告効果測定ツール。主に以下の機能を提供する:
- アトリビューション計測 - どの広告からインストールされたかを特定
- イベント計測 - アプリ内の行動(課金、登録など)を追跡
- 不正検知 - 広告詐欺からの保護
- ディープリンク - 広告から特定の画面への誘導
広告流入計測の仕組み
計測の流れ
1. ユーザーが広告をクリック
↓
2. Adjustのトラッカーリンクを経由
↓
3. アプリストアへリダイレクト
↓
4. ユーザーがアプリをインストール
↓
5. アプリ起動時にAdjust SDKが初期化
↓
6. Adjustサーバーがクリックとインストールをマッチング
アトリビューションの仕組み
Adjustは「クリック」と「インストール」を紐付けて、どの広告経由かを特定する。紐付けには以下の情報を使う:
- デバイスID(GAID/IDFA)- 最も正確
- フィンガープリント - IPアドレス、端末情報などの組み合わせ
- Google Play Referrer - Playストアからのリファラー情報
iOS 14.5以降はATT(App Tracking Transparency)の同意が必要。同意がない場合はSKAdNetworkを使った計測になる。
トラッカーURL(リンク)の構造
広告に設置するリンクは以下の構造:
https://app.adjust.com/{リンクトークン}?campaign={キャンペーン名}&adgroup={広告グループ}&creative={クリエイティブ}
例:
https://app.adjust.com/abc123?campaign=summer_sale&adgroup=twitter_feed&creative=video_01
パラメータの階層
| レベル | パラメータ | 用途 |
|---|---|---|
| ネットワーク | リンク名 | 広告媒体(Google, Facebook等) |
| キャンペーン | campaign= | キャンペーン識別 |
| アドグループ | adgroup= | 広告グループ・配置 |
| クリエイティブ | creative= | 広告素材の識別 |
注意点:
- パラメータは小文字のみ
- リンクレベルは作成後に変更不可
- 空のパラメータは「unknown」と表示される
広告リンクの配布パターン
広告運用側がどのようにリンクを使うかは様々。以下のパターンに対応できる。
1. 広告プラットフォーム経由
Google Ads、Facebook/Meta、Twitter/Xなどの広告管理画面でトラッカーURLを設定。
ユーザー → 広告クリック → Adjustリンク → ストア → インストール
プラットフォーム側でマクロ(動的パラメータ)を使えば、キャンペーン名などを自動挿入できる。
2. 自社メディア・ブログ
記事やバナーに直接リンクを設置。
<a href="https://app.adjust.com/abc123?campaign=blog_post">
アプリをダウンロード
</a>
3. インフルエンサー・アフィリエイト
個別のトラッカーリンクを発行して成果を分けて計測。
インフルエンサーA用: https://app.adjust.com/abc123?campaign=influencer_a
インフルエンサーB用: https://app.adjust.com/def456?campaign=influencer_b
4. QRコード
オフライン広告(チラシ、ポスター、店頭POP)からの流入計測。
QRコード → Adjustリンク → ストア → インストール
QRコードの中身はトラッカーURL。印刷媒体ごとにリンクを分けることで、どの媒体が効果的かを計測可能。
5. スマートバナー(Webサイト)
Webサイト訪問者にアプリインストールを促すバナー。Adjustはスマートバナー機能を提供している。
Webサイト訪問 → スマートバナー表示 → タップ → ストア → インストール
6. ディープリンク(既存ユーザー向け)
既にアプリをインストール済みのユーザーを特定の画面に誘導。
広告クリック → アプリが開く → 特定の画面へ遷移
新規インストールの場合は「ディファードディープリンク」で、インストール後に特定画面を開ける。
アプリ側で必要な対応
広告がどう張られるかに関わらず、アプリ側でやることは同じ:
- Adjust SDKを初期化 - これだけでインストール計測は動く
- 広告IDを取得できるようにする - 必要に応じてバックエンドに送信
- イベント計測を実装 - 登録、課金などのコンバージョン計測
- ディープリンク対応(必要なら)- 特定画面への誘導
広告運用側がどのパターンで配信しても、SDK側の実装は変わらない。
Flutter SDKの導入
1. パッケージの追加
pubspec.yamlに追加:
dependencies:
adjust_sdk: ^5.5.0
インストール:
flutter pub get
2. Androidの設定
android/app/build.gradle:
dependencies {
implementation 'com.google.android.gms:play-services-ads-identifier:18.1.0'
implementation 'com.android.installreferrer:installreferrer:2.2'
}
android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
3. iOSの設定
ios/Runner/Info.plistにATTの説明を追加:
<key>NSUserTrackingUsageDescription</key>
<string>パーソナライズされた広告を表示するために使用します</string>
Xcodeで以下のフレームワークを追加:
AdSupport.frameworkStoreKit.frameworkAppTrackingTransparency.framework
4. SDKの初期化
import 'package:adjust_sdk/adjust.dart';
import 'package:adjust_sdk/adjust_config.dart';
void main() {
// Adjust初期化
AdjustConfig config = AdjustConfig(
'{YourAppToken}', // Adjust管理画面で取得
AdjustEnvironment.sandbox, // 本番時は AdjustEnvironment.production
);
// ログレベル(開発時のみ)
config.logLevel = AdjustLogLevel.verbose;
Adjust.initSdk(config);
runApp(MyApp());
}
環境の切り替え:
- 開発・テスト時:
AdjustEnvironment.sandbox - 本番リリース時:
AdjustEnvironment.production
イベント計測の実装
WebViewアプリの場合、イベント計測には2つのアプローチがある:
- Flutter側で計測 - WebViewからFlutterにイベントを通知し、Flutter側でAdjust SDKを呼ぶ
- サーバー側で計測 - WebViewからサーバーAPIを呼び、サーバーからAdjust Server-to-Server APIを使う
ここではFlutter側で計測する方法を説明する。サーバー側計測は広告IDをサーバーに送っておけば、サーバー側でAdjust APIを直接叩く形になる。
基本的なイベント送信
Adjust管理画面でイベントトークンを作成し、アプリ内でトラッキング:
import 'package:adjust_sdk/adjust.dart';
import 'package:adjust_sdk/adjust_event.dart';
// ボタンタップなどのイベント
void trackButtonClick() {
AdjustEvent event = AdjustEvent('{EventToken}');
Adjust.trackEvent(event);
}
収益イベント(課金計測)
void trackPurchase(double amount, String currency, String transactionId) {
AdjustEvent event = AdjustEvent('{RevenueEventToken}');
// 金額と通貨を設定
event.setRevenue(amount, currency);
// 重複防止用のトランザクションID
event.deduplicationId = transactionId;
Adjust.trackEvent(event);
}
// 使用例
trackPurchase(980, 'JPY', 'order_12345');
カスタムパラメータの追加
void trackEventWithParams() {
AdjustEvent event = AdjustEvent('{EventToken}');
// サーバーコールバック用パラメータ
event.addCallbackParameter('user_id', '12345');
event.addCallbackParameter('item_id', 'abc');
// パートナー連携用パラメータ
event.addPartnerParameter('product_id', 'xyz');
Adjust.trackEvent(event);
}
WebViewからイベントを通知する
WebView内で発生したイベント(登録完了、課金完了など)をFlutter側に通知し、Adjust SDKで計測する例:
// Flutter側
webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'AdjustBridge',
onMessageReceived: (message) {
// JSONでイベント情報を受け取る
final data = jsonDecode(message.message);
final eventType = data['type'];
switch (eventType) {
case 'registration':
trackRegistration(data['userId']);
break;
case 'purchase':
trackPurchase(
data['amount'].toDouble(),
data['currency'],
data['transactionId'],
);
break;
}
},
);
void trackRegistration(String userId) {
AdjustEvent event = AdjustEvent('{RegistrationEventToken}');
event.addCallbackParameter('user_id', userId);
Adjust.trackEvent(event);
}
// Web側(登録完了時)
function onRegistrationComplete(userId) {
if (window.AdjustBridge) {
AdjustBridge.postMessage(JSON.stringify({
type: 'registration',
userId: userId
}));
}
}
// Web側(課金完了時)
function onPurchaseComplete(amount, currency, transactionId) {
if (window.AdjustBridge) {
AdjustBridge.postMessage(JSON.stringify({
type: 'purchase',
amount: amount,
currency: currency,
transactionId: transactionId
}));
}
}
アトリビューション情報の取得
どの広告からインストールされたかをアプリ内で取得:
AdjustConfig config = AdjustConfig(
'{YourAppToken}',
AdjustEnvironment.sandbox,
);
// アトリビューションコールバック
config.attributionCallback = (AdjustAttribution attribution) {
print('Network: ${attribution.network}');
print('Campaign: ${attribution.campaign}');
print('Adgroup: ${attribution.adgroup}');
print('Creative: ${attribution.creative}');
};
Adjust.initSdk(config);
テスト方法
1. ログの確認
AdjustLogLevel.verboseを設定し、ログで以下を確認:
Install tracked- インストール計測成功Event tracked- イベント計測成功gps_adid- 広告IDが取得できている
2. Adjust管理画面での確認
- Testing Consoleでデバイスを登録
- テストリンクをクリック
- アプリをインストール・起動
- 管理画面のリアルタイムデータで確認
3. サンドボックス環境
本番データを汚さないよう、開発中は必ずsandbox環境を使用。
広告媒体との連携
Google Adsとの連携
- Adjust管理画面でGoogle Adsネットワークを追加
- リンクIDをGoogle Adsに設定
- コンバージョンアクションを作成
Facebook/Metaとの連携
https://app.adjust.com/abc123?campaign={{campaign.name}}&adgroup={{adset.name}}&creative={{ad.name}}
Facebookのマクロを使ってパラメータを動的に設定できる。
WebViewアプリでの広告ID連携
FlutterでWebViewを使ったアプリの場合、登録などの処理がWeb側で行われることが多い。その場合、Flutter側で取得した広告IDをWebView側に渡す必要がある。
広告IDの取得
Adjust SDKから直接広告IDを取得できる:
import 'dart:io';
import 'package:adjust_sdk/adjust.dart';
Future<String?> getAdvertisingId() async {
if (Platform.isAndroid) {
return await Adjust.getGoogleAdId();
} else if (Platform.isIOS) {
return await Adjust.getIdfa();
}
return null;
}
方法1: URLパラメータで渡す
最もシンプルな方法。WebViewで読み込むURLにクエリパラメータとして付与する。
Future<void> loadWebView() async {
String? adId = await getAdvertisingId();
String baseUrl = 'https://example.com/register';
String url = adId != null
? '$baseUrl?ad_id=$adId'
: baseUrl;
webViewController.loadRequest(Uri.parse(url));
}
Web側ではURLSearchParamsで取得:
const params = new URLSearchParams(window.location.search);
const adId = params.get('ad_id');
// 登録APIに含める
fetch('/api/register', {
method: 'POST',
body: JSON.stringify({ advertisingId: adId, ...formData })
});
メリット: 実装が簡単、Web側の変更が少ない デメリット: URLに露出する、ページ遷移で引き継ぎが必要
方法2: JavaScriptチャネルで渡す
Flutter側からJavaScriptを実行して、グローバル変数やlocalStorageに広告IDを設定する。
import 'package:webview_flutter/webview_flutter.dart';
late WebViewController webViewController;
Future<void> initWebView() async {
String? adId = await getAdvertisingId();
webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageFinished: (url) {
// ページ読み込み完了後に広告IDを注入
if (adId != null) {
webViewController.runJavaScript('''
window.flutterAdvertisingId = "$adId";
localStorage.setItem("advertisingId", "$adId");
''');
}
},
),
)
..loadRequest(Uri.parse('https://example.com'));
}
Web側での取得:
// グローバル変数から取得
const adId = window.flutterAdvertisingId;
// または localStorage から取得
const adId = localStorage.getItem('advertisingId');
メリット: URLに露出しない、ページ遷移しても保持される(localStorage) デメリット: タイミング制御が必要(ページ読み込み完了後に注入)
方法3: JavaScriptチャネルで双方向通信
Web側からFlutter側に広告IDをリクエストする方式。
Future<void> initWebView() async {
webViewController = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'FlutterBridge',
onMessageReceived: (message) async {
if (message.message == 'getAdvertisingId') {
String? adId = await getAdvertisingId();
webViewController.runJavaScript(
'window.onAdvertisingIdReceived("${adId ?? ""}")'
);
}
},
)
..loadRequest(Uri.parse('https://example.com'));
}
Web側:
// 広告IDをリクエスト
function requestAdvertisingId() {
if (window.FlutterBridge) {
FlutterBridge.postMessage('getAdvertisingId');
}
}
// コールバックで受け取る
window.onAdvertisingIdReceived = function(adId) {
console.log('Received advertising ID:', adId);
// 登録処理に使用
};
// ページ読み込み時にリクエスト
document.addEventListener('DOMContentLoaded', requestAdvertisingId);
メリット: Web側が必要なタイミングで取得できる、柔軟性が高い デメリット: 実装が複雑、Web側にFlutter依存のコードが必要
方法4: UserAgentに含める
カスタムUserAgentに広告IDを含める方法。サーバー側でUserAgentをパースして取得する。
Future<void> initWebView() async {
String? adId = await getAdvertisingId();
String customUserAgent = 'MyApp/1.0 AdId/${adId ?? "unknown"}';
webViewController = WebViewController()
..setUserAgent(customUserAgent)
..loadRequest(Uri.parse('https://example.com'));
}
サーバー側(例: Node.js):
app.post('/api/register', (req, res) => {
const userAgent = req.headers['user-agent'];
const match = userAgent.match(/AdId\/([^\s]+)/);
const adId = match ? match[1] : null;
// 登録処理
});
メリット: 全リクエストに自動で含まれる、Web側のJS変更不要 デメリット: サーバー側でのパース処理が必要
方法5: Cookieを使う
初回アクセス時にFlutter側でCookieを設定し、Web側で読み取る。
import 'package:webview_flutter/webview_flutter.dart';
Future<void> initWebView() async {
String? adId = await getAdvertisingId();
final cookieManager = WebViewCookieManager();
if (adId != null) {
await cookieManager.setCookie(
WebViewCookie(
name: 'advertising_id',
value: adId,
domain: 'example.com',
path: '/',
),
);
}
webViewController = WebViewController()
..loadRequest(Uri.parse('https://example.com'));
}
Web側:
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
const adId = getCookie('advertising_id');
メリット: ドメイン全体で利用可能、ページ遷移しても保持 デメリット: Cookieの制限に注意(サイズ、SameSiteなど)
どの方法を選ぶか
| 方法 | 実装難易度 | Web側変更 | おすすめケース |
|---|---|---|---|
| URLパラメータ | 簡単 | 少 | 単一ページで完結する登録 |
| JS注入 | 普通 | 中 | SPA、ページ遷移あり |
| 双方向通信 | やや複雑 | 多 | 複雑な連携が必要な場合 |
| UserAgent | 簡単 | なし | サーバー側で処理したい場合 |
| Cookie | 普通 | 少 | 複数ドメインで共有したい場合 |
まとめ
Adjustを使った広告流入計測の基本的な流れ:
- Adjust管理画面でアプリを登録し、アプリトークンを取得
- トラッカーリンクを作成し、広告に設置
- Flutter SDKを導入・初期化
- 必要に応じてイベント計測を実装
- サンドボックス環境でテスト
- 本番環境に切り替えてリリース
次のステップとしては、実際にAdjust管理画面でアプリを登録し、サンドボックス環境でテストしてみよう。