Flutter SDK - 광고 구매 추적

문서

광고 구매 어트리뷰션

미디에이션 플랫폼의 광고 구매을 추적하고 사용자를 앱으로 유도한 특정 마케팅 캠페인에 어트리뷰션하여 종합적인 ROI 분석을 가능하게 합니다.

광고 구매 어트리뷰션은 모바일 앱 광고 구매을 사용자를 생성한 마케팅 캠페인에 연결하여 캠페인 비용, 인앱 구매, 광고 구매을 연동 보고에 결합합니다. 또한 이 데이터는 캠페인 성과를 최적화하기 위해 애드 네트워크로 다시 전달됩니다.

자세히 알아보기: 어트리뷰션 방법론과 지원되는 미디에이션 플랫폼에 대한 자세한 내용은 Singular 광고 구매 어트리뷰션 FAQ를 참조하세요.


광고 구매 어트리뷰션의 작동 방식

어트리뷰션 흐름

미디에이션 플랫폼은 콜백을 통해 노출 수준 또는 사용자 수준의 구매 데이터를 보고하고, 이를 확인하여 어트리뷰션 분석을 위해 Singular에 전달합니다.

  • 캠페인 어트리뷰션: 광고 구매을 각 사용자를 확보한 특정 캠페인에 연결하여 캠페인당 실제 ROI를 보여줍니다.
  • 데이터 소스: 구매 데이터는 사용자 수준 또는 노출 수준 세분화로 미디에이션 플랫폼에서 가져옵니다.
  • 네트워크 최적화: Singular는 타겟팅 및 입찰 전략을 개선하기 위해 구매 데이터를 애드 네트워크에 다시 전달합니다.

구현 요구 사항

잘못된 구매 데이터는 전송 후 수정할 수 없으므로 광고 구매 추적을 구현하기 전에 데이터 정확성을 확인해야 합니다.

중요 요구 사항:

  • 통화 코드: 세 글자로 구성된 ISO 4217 코드(USD, EUR, INR)를 사용하세요. 대부분의 미디에이션 플랫폼은 기본적으로 USD를 사용합니다.
  • 데이터 유효성 검사: Singular로 전송하기 전에 항상 구매 값이 양수이고 통화 코드가 비어 있지 않은지 확인합니다.
  • 단위 변환: 일부 플랫폼은 구매을 마이크로 단위(1,000,000 = $1.00)로 보고합니다. Singular로 보내기 전에 달러로 변환하세요.

설정 단계

미디에이션 플랫폼에서 광고 구매 어트리뷰션을 구현하려면 다음 단계를 따르세요.

  1. SDK 업데이트: 최신 버전의 Singular 플러터 SDK를 실행하고 있는지 확인합니다.
  2. 플랫폼 구성: 미디에이션 플랫폼 대시보드에서 광고 구매 리포팅을 활성화합니다(AdMob, AppLovin 등).
  3. 콜백을 구현합니다: 미디에이션 SDK에서 구매 이벤트 리스너를 추가하여 노출 데이터를 캡처합니다.
  4. 데이터 유효성 검사: Singular로 전달하기 전에 구매이 0보다 크고 통화가 유효한지 확인합니다.
  5. 연동 테스트: 24시간 이내에 Singular 리포트에 구매 데이터가 표시되는지 확인합니다.

애드몹 연동

전제 조건

Singular와 연동하기 전에 AdMob 계정에서 광고 구매 보고를 활성화하고 Flutter용 Google 모바일 광고 SDK를 구현하세요.

  • AdMob 설정: AdMob 대시보드에서 노출 수준 광고 구매을 활성화합니다. AdMob 지원참조
  • Flutter 패키지: google_mobile_ads 패키지를 설치합니다. 시작 가이드를참조하세요.

플랫폼 차이점: 애드몹은 플랫폼별로 구매을 다르게 보고합니다. Android는 마이크로 단위(5000 = $0.005)로 구매을 반환하고, iOS는 소수점 값(0.005 = $0.005)으로 구매을 반환합니다. Singular로 보내기 전에 Android 값을 1,000,000으로 나누어 변환합니다.


구현 단계

광고를 로드할 때 onPaidEvent 콜백을 설정하여 성공적인 광고 노출에서 구매 데이터를 캡처합니다.

  1. 광고를 로드합니다: 광고 단위(보상형, 전면 광고, 배너 등)를 생성하고 로드합니다.
  2. 콜백 설정: 광고에서 구매이 발생하면 onPaidEvent 콜백을 할당하여 AdValue 을 캡처합니다.
  3. 단위 변환: 단위 전환: valueMicros 을 1,000,000 으로 나누어 달러로 전환합니다.
  4. 유효성 검사: 구매이 0을 초과하고 통화가 비어 있지 않은지 확인합니다.
  5. Singular로 보내기: 유효성 검사된 데이터로 Singular.adRevenue() 으로 전화

리워드 광고 예시

광고 로드가 완료된 후 onPaidEvent 콜백을 설정하여 리워드형 동영상 광고에서 광고 구매을 확보하세요.

Dart
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:singular_flutter_sdk/singular.dart';

const String adUnitId = 'YOUR_AD_UNIT_ID';

class AdManager {
  RewardedAd? _rewardedAd;

  void loadRewardedAd() {
    RewardedAd.load(
      adUnitId: adUnitId,
      request: AdRequest(),
      rewardedAdLoadCallback: RewardedAdLoadCallback(
        onAdLoaded: (RewardedAd ad) {
          _rewardedAd = ad;
          print('Rewarded ad loaded successfully');

          // Set up full screen content callback
          _rewardedAd?.fullScreenContentCallback = FullScreenContentCallback(
            onAdShowedFullScreenContent: () {
              print('Rewarded ad displayed');
            },
            onAdFailedToShowFullScreenContent: (AdError adError) {
              print('Rewarded ad failed to show: ${adError.message}');
            },
            onAdDismissedFullScreenContent: () {
              print('Rewarded ad dismissed');
              _rewardedAd = null;
            },
          );

          // Set up paid event callback for revenue tracking
          _rewardedAd?.onPaidEvent = (AdValue adValue) {
            // Convert revenue from micros to dollars
            double revenue = adValue.valueMicros / 1_000_000.0;
            String? currency = adValue.currencyCode;

            // Validate revenue and currency before sending
            if (revenue > 0 && currency != null && currency.isNotEmpty) {
              final adData = {
                'adPlatform': 'AdMob',
                'currency': currency,
                'revenue': revenue,
              };

              // Send ad revenue data to Singular
              Singular.adRevenue(adData);

              // Log for debugging
              print('Ad Revenue reported: $revenue $currency');
            } else {
              print('Invalid ad revenue: revenue=$revenue, currency=$currency');
            }
          };
        },
        onAdFailedToLoad: (LoadAdError loadAdError) {
          print('Rewarded ad failed to load: ${loadAdError.message}');
        },
      ),
    );
  }

  void showRewardedAd() {
    if (_rewardedAd != null) {
      _rewardedAd!.show(
        onUserEarnedReward: (AdWithoutView ad, RewardItem reward) {
          print('User earned reward: ${reward.amount} ${reward.type}');
        },
      );
    } else {
      print('Rewarded ad not ready');
    }
  }
}

전면 광고 예시

동일한 onPaidEvent 패턴을 사용하여 중간 광고의 구매을 추적합니다.

Dart
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:singular_flutter_sdk/singular.dart';

const String interstitialAdUnitId = 'YOUR_INTERSTITIAL_AD_UNIT_ID';

class InterstitialAdManager {
  InterstitialAd? _interstitialAd;

  void loadInterstitialAd() {
    InterstitialAd.load(
      adUnitId: interstitialAdUnitId,
      request: AdRequest(),
      adLoadCallback: InterstitialAdLoadCallback(
        onAdLoaded: (InterstitialAd ad) {
          _interstitialAd = ad;
          print('Interstitial ad loaded');

          // Set paid event callback
          _interstitialAd?.onPaidEvent = (AdValue adValue) {
            double revenue = adValue.valueMicros / 1_000_000.0;
            String? currency = adValue.currencyCode;

            if (revenue > 0 && currency != null && currency.isNotEmpty) {
              Singular.adRevenue({
                'adPlatform': 'AdMob',
                'currency': currency,
                'revenue': revenue,
              });
              
              print('Interstitial revenue: $revenue $currency');
            }
          };

          // Set full screen callback
          _interstitialAd?.fullScreenContentCallback = FullScreenContentCallback(
            onAdDismissedFullScreenContent: () {
              _interstitialAd = null;
              loadInterstitialAd(); // Load next ad
            },
          );
        },
        onAdFailedToLoad: (LoadAdError error) {
          print('Interstitial ad failed to load: ${error.message}');
        },
      ),
    );
  }

  void showInterstitialAd() {
    _interstitialAd?.show();
  }
}

배너 광고 예시

Flutter UI에 표시되는 배너 광고의 구매을 추적합니다.

Dart
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:singular_flutter_sdk/singular.dart';

const String bannerAdUnitId = 'YOUR_BANNER_AD_UNIT_ID';

class BannerAdWidget extends StatefulWidget {
  @override
  _BannerAdWidgetState createState() => _BannerAdWidgetState();
}

class _BannerAdWidgetState extends State<BannerAdWidget> {
  BannerAd? _bannerAd;
  bool _isBannerAdReady = false;

  @override
  void initState() {
    super.initState();
    _loadBannerAd();
  }

  void _loadBannerAd() {
    _bannerAd = BannerAd(
      adUnitId: bannerAdUnitId,
      size: AdSize.banner,
      request: AdRequest(),
      listener: BannerAdListener(
        onAdLoaded: (Ad ad) {
          setState(() {
            _isBannerAdReady = true;
          });
          print('Banner ad loaded');

          // Set paid event callback
          (ad as BannerAd).onPaidEvent = (AdValue adValue) {
            double revenue = adValue.valueMicros / 1_000_000.0;
            String? currency = adValue.currencyCode;

            if (revenue > 0 && currency != null && currency.isNotEmpty) {
              Singular.adRevenue({
                'adPlatform': 'AdMob',
                'currency': currency,
                'revenue': revenue,
              });
              
              print('Banner revenue: $revenue $currency');
            }
          };
        },
        onAdFailedToLoad: (Ad ad, LoadAdError error) {
          ad.dispose();
          print('Banner ad failed to load: ${error.message}');
        },
      ),
    );

    _bannerAd?.load();
  }

  @override
  void dispose() {
    _bannerAd?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_isBannerAdReady && _bannerAd != null) {
      return Container(
        alignment: Alignment.center,
        width: _bannerAd!.size.width.toDouble(),
        height: _bannerAd!.size.height.toDouble(),
        child: AdWidget(ad: _bannerAd!),
      );
    }
    return SizedBox.shrink();
  }
}

앱러빈 MAX 연동

전제 조건

Singular와 연동하기 전에 앱러빈 MAX Flutter 플러그인을 구현하고 노출 수준 구매 추적을 구성합니다.

  • MAX 설정: 앱러빈 MAX 대시보드에서 앱을 구성합니다.
  • Flutter 플러그인: 앱러빈_맥스 패키지를 설치합니다. 시작 가이드를참조하세요.
  • 구매 API: MAX 대시보드 설정에서 노출 수준 사용자 구매 API 활성화하기

구현 개요

각 광고 형식에 대해 onAdRevenuePaidCallback 리스너를 설정하여 노출이 구매을 창출할 때 구매을 캡처합니다.

  1. MAX 초기화: SDK 키로 앱러빈 MAX SDK를 구성합니다.
  2. 리스너 설정: 각 광고 형식에 대해 onAdRevenuePaidCallback 추가
  3. 구매 추출: ad.revenue 속성에서 구매 가져오기
  4. 통화 처리: AppLovin은 일반적으로 USD로 보고하지만 ad.currency으로 확인합니다.
  5. Singular로 전달: Singular.adRevenue() 으로 검증된 데이터를 전송합니다.

MAX 구현 완료

연동 핸들러를 사용하여 모든 MAX 광고 형식(보상형, 전면 광고, 배너, MREC)에서 구매을 확보하세요.

Dart
import 'package:flutter/material.dart';
import 'package:applovin_max/applovin_max.dart';
import 'package:singular_flutter_sdk/singular.dart';

class AppLovinMaxManager extends StatefulWidget {
  @override
  _AppLovinMaxManagerState createState() => _AppLovinMaxManagerState();
}

class _AppLovinMaxManagerState extends State<AppLovinMaxManager> {
  @override
  void initState() {
    super.initState();
    _initializeAppLovinMax();
  }

  Future<void> _initializeAppLovinMax() async {
    // Initialize AppLovin MAX SDK
    await AppLovinMAX.initialize('YOUR_SDK_KEY');

    // Set up revenue listeners for all ad formats
    _setupRewardedAdListeners();
    _setupInterstitialAdListeners();
    _setupBannerAdListeners();
    _setupMRecAdListeners();

    print('AppLovin MAX initialized with revenue tracking');
  }

  void _setupRewardedAdListeners() {
    AppLovinMAX.setRewardedAdListener(RewardedAdListener(
      onAdRevenuePaidCallback: (ad) {
        _handleAdRevenuePaid(ad, 'Rewarded');
      },
      onAdLoadedCallback: (ad) {
        print('Rewarded ad loaded');
      },
      onAdLoadFailedCallback: (adUnitId, error) {
        print('Rewarded ad failed to load: $error');
      },
    ));
  }

  void _setupInterstitialAdListeners() {
    AppLovinMAX.setInterstitialListener(InterstitialListener(
      onAdRevenuePaidCallback: (ad) {
        _handleAdRevenuePaid(ad, 'Interstitial');
      },
      onAdLoadedCallback: (ad) {
        print('Interstitial ad loaded');
      },
      onAdLoadFailedCallback: (adUnitId, error) {
        print('Interstitial ad failed to load: $error');
      },
    ));
  }

  void _setupBannerAdListeners() {
    AppLovinMAX.setBannerListener(AdViewAdListener(
      onAdRevenuePaidCallback: (ad) {
        _handleAdRevenuePaid(ad, 'Banner');
      },
      onAdLoadedCallback: (ad) {
        print('Banner ad loaded');
      },
      onAdLoadFailedCallback: (adUnitId, error) {
        print('Banner ad failed to load: $error');
      },
    ));
  }

  void _setupMRecAdListeners() {
    AppLovinMAX.setMRecListener(AdViewAdListener(
      onAdRevenuePaidCallback: (ad) {
        _handleAdRevenuePaid(ad, 'MREC');
      },
      onAdLoadedCallback: (ad) {
        print('MREC ad loaded');
      },
      onAdLoadFailedCallback: (adUnitId, error) {
        print('MREC ad failed to load: $error');
      },
    ));
  }

  void _handleAdRevenuePaid(Ad ad, String adType) {
    // Extract revenue and currency from ad object
    final double revenueValue = ad.revenue ?? 0.0;
    final String currency = ad.revenueCurrency ?? 'USD';

    // Validate revenue before sending
    if (revenueValue > 0) {
      final adData = {
        'adPlatform': 'AppLovin',
        'currency': currency,
        'revenue': revenueValue,
      };

      // Send ad revenue to Singular
      Singular.adRevenue(adData);

      // Log for debugging
      print('[$adType] Revenue: $revenueValue $currency');
    } else {
      print('[$adType] Invalid revenue: $revenueValue');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AppLovin MAX Revenue Tracking'),
      ),
      body: Center(
        child: Text('MAX revenue tracking active'),
      ),
    );
  }
}

모범 사례: 광고를 로드하기 전에 앱 초기화 중에 광고 구매 리스너를 설정하여 노출이 누락되지 않도록 하세요.


IronSource(유니티 레벨플레이) 연동

전제 조건

Singular와 연동하기 전에 IronSource 대시보드에서 ILR(노출 수준 구매)을 활성화하고 IronSource Flutter 플러그인을 구현하세요.

  • IronSource 설정: IronSource 대시보드에서 ARM SDK 포스트백 플래그를 활성화합니다.
  • Flutter 플러그인: ironsource_mediation 패키지를 설치합니다. 시작 가이드를참조하세요.
  • ILR API: 앱에 대한 노출 수준 구매 추적 구성하기

구현 예시

onImpressionDataSuccess 콜백을 사용하여 아이언소스 미디에이션에서 노출 수준 구매 데이터를 캡처합니다.

Dart
import 'package:ironsource_mediation/ironsource_mediation.dart';
import 'package:singular_flutter_sdk/singular.dart';

void setupIronSourceRevenueTracking() {
  // Set up impression data listener
  IronSource.setImpressionDataListener((ISImpressionData? impressionData) {
    onImpressionDataSuccess(impressionData);
  });
}

void onImpressionDataSuccess(ISImpressionData? impressionData) {
  // Ensure impression data is not null
  if (impressionData == null) {
    print('No impression data available');
    return;
  }

  // Extract and validate revenue
  final revenue = impressionData.revenue?.toDouble() ?? 0.0;
  
  if (revenue <= 0) {
    print('Invalid revenue value: $revenue');
    return;
  }

  // Create ad revenue data for Singular
  final adData = {
    'adPlatform': 'IronSource',
    'currency': 'USD',
    'revenue': revenue,
  };

  // Send to Singular
  Singular.adRevenue(adData);

  // Log for debugging
  print('IronSource Revenue: $revenue USD');
}

트레이드플러스 연동

전제 조건

광고에서 구매이 발생할 때 eCPM 데이터를 캡처하도록 TradPlus 노출 위임자를 설정합니다.

  • 트레이드플러스 설정: TradPlus 대시보드에서 앱을 구성합니다.
  • 노출 델리게이트: 트레이드플러스 설정에서 노출 수준 추적을 활성화합니다.

eCPM 전환: TradPlus는 일반적으로 밀리 단위로 eCPM을 보고합니다. Singular로 보내기 전에 1000.0으로 나누어 달러로 변환합니다.


구현 예시

글로벌 노출 리스너를 사용하여 모든 TradPlus 광고 형식에서 구매 데이터를 캡처합니다.

Dart
import 'package:singular_flutter_sdk/singular.dart';

void setupTradPlusImpressionListener() {
  // Set up global impression listener
  TradPlusSdk.setGlobalImpressionListener((tpAdInfo) {
    if (tpAdInfo == null) {
      print('AdInfo is null');
      return;
    }

    // Ensure eCPM is available
    if (tpAdInfo.ecpm == null) {
      print('eCPM value is null');
      return;
    }

    // Convert eCPM from milli-units to dollars
    double revenue = tpAdInfo.ecpm! / 1000.0;

    // Validate revenue
    if (revenue <= 0) {
      print('Invalid revenue: $revenue');
      return;
    }

    // Create ad revenue data
    final adData = {
      'adPlatform': 'TradPlus',
      'currency': 'USD',
      'revenue': revenue,
    };

    // Send to Singular
    Singular.adRevenue(adData);

    // Log for debugging
    print('TradPlus Revenue: $revenue USD');
  });
}

void main() {
  // Initialize TradPlus SDK
  setupTradPlusImpressionListener();
  
  runApp(MyApp());
}

일반 연동(기타 플랫폼)

맞춤 구현

위에서 명시적으로 다루지 않은 미디에이션 플랫폼의 경우, Singular 광고 구매 방식을 사용하여 사용자 지정 구매 처리기를 구현하세요.

Dart
import 'package:singular_flutter_sdk/singular.dart';

/// Generic function to report ad revenue to Singular
/// Use this for any mediation platform not explicitly supported above
void reportAdRevenue({
  required String adPlatform,
  required String currency,
  required double revenue,
}) {
  // Validate revenue value
  if (revenue <= 0) {
    print('Invalid revenue value: $revenue');
    return;
  }

  // Validate currency code
  if (currency.isEmpty) {
    print('Currency code is empty');
    return;
  }

  // Create ad revenue data
  final adData = {
    'adPlatform': adPlatform,
    'currency': currency,
    'revenue': revenue,
  };

  // Send to Singular
  Singular.adRevenue(adData);

  // Log for debugging
  print('Ad Revenue reported: $revenue $currency from $adPlatform');
}

// Example usage with a custom mediation platform
void onCustomAdImpression(Map<String, dynamic> impressionData) {
  reportAdRevenue(
    adPlatform: 'CustomNetwork',
    currency: impressionData['currency'] ?? 'USD',
    revenue: impressionData['revenue'] ?? 0.0,
  );
}

모범 사례 및 문제 해결

데이터 유효성 검사

  • 항상 유효성 검사: Singular로 전송하기 전에 구매이 0보다 크고 통화가 비어 있지 않은지 확인합니다.
  • 단위 전환: 플랫폼이 마이크로 또는 달러로 보고하는지 확인하고 그에 따라 변환하세요.
  • 통화 코드: ISO 4217 세 글자 코드(USD, EUR, JPY)를 일관되게 사용하세요.
  • 철저하게 테스트하세요: 프로덕션 릴리스 전에 매출 데이터가 Singular 보고에 표시되는지 확인합니다.

일반적인 문제

  • 누락된 구매: 미디에이션 플랫폼 대시보드에서 노출 수준 구매이 활성화되어 있는지 확인합니다.
  • 잘못된 값: 특정 플랫폼의 단위 전환(마이크로 대 달러)을 확인하세요.
  • 보고서에 데이터가 없습니다: 구현 후 Singular 리포트에 데이터가 표시될 때까지 24-48시간이 소요됩니다.
  • 구매이 없음: 광고 로드가 완료되기 전에 광고 콜백이 올바르게 설정되었는지 확인합니다.

중요: 광고 구매 데이터는 전송 후에는 수정할 수 없습니다. Singular.adRevenue() 을 호출하기 전에 항상 데이터 정확성을 확인하세요.