技術 約11分で読めます

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. ディープリンク(既存ユーザー向け)

既にアプリをインストール済みのユーザーを特定の画面に誘導。

広告クリック → アプリが開く → 特定の画面へ遷移

新規インストールの場合は「ディファードディープリンク」で、インストール後に特定画面を開ける。

アプリ側で必要な対応

広告がどう張られるかに関わらず、アプリ側でやることは同じ:

  1. Adjust SDKを初期化 - これだけでインストール計測は動く
  2. 広告IDを取得できるようにする - 必要に応じてバックエンドに送信
  3. イベント計測を実装 - 登録、課金などのコンバージョン計測
  4. ディープリンク対応(必要なら)- 特定画面への誘導

広告運用側がどのパターンで配信しても、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.framework
  • StoreKit.framework
  • AppTrackingTransparency.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つのアプローチがある:

  1. Flutter側で計測 - WebViewからFlutterにイベントを通知し、Flutter側でAdjust SDKを呼ぶ
  2. サーバー側で計測 - 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管理画面での確認

  1. Testing Consoleでデバイスを登録
  2. テストリンクをクリック
  3. アプリをインストール・起動
  4. 管理画面のリアルタイムデータで確認

3. サンドボックス環境

本番データを汚さないよう、開発中は必ずsandbox環境を使用。

広告媒体との連携

Google Adsとの連携

  1. Adjust管理画面でGoogle Adsネットワークを追加
  2. リンクIDをGoogle Adsに設定
  3. コンバージョンアクションを作成

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を使った広告流入計測の基本的な流れ:

  1. Adjust管理画面でアプリを登録し、アプリトークンを取得
  2. トラッカーリンクを作成し、広告に設置
  3. Flutter SDKを導入・初期化
  4. 必要に応じてイベント計測を実装
  5. サンドボックス環境でテスト
  6. 本番環境に切り替えてリリース

次のステップとしては、実際にAdjust管理画面でアプリを登録し、サンドボックス環境でテストしてみよう。

参考リンク