Android 및 iOS의 기본 설정

네이티브 광고는 플랫폼 고유의 UI 구성요소(예: Android의 View 또는 iOS의 UIView)를 사용하여 사용자에게 표시됩니다.

이 가이드에서는 플랫폼별 코드를 사용하여 네이티브 광고를 로드, 표시, 맞춤설정하는 방법을 보여줍니다.

기본 요건

항상 테스트 광고로 테스트

앱을 빌드하고 테스트할 때는 운영 중인 실제 광고 대신 테스트 광고를 사용하세요. 테스트 광고를 로드하는 가장 쉬운 방법은 네이티브 광고 전용 테스트 광고 단위 ID를 사용하는 것입니다.

Android

ca-app-pub-3940256099942544/2247696110

iOS

ca-app-pub-3940256099942544/3986624511

테스트 광고 단위는 모든 요청에 대해 테스트 광고를 반환하도록 구성되므로 코딩, 테스트, 디버깅 중에 앱에서 사용할 수 있습니다. 단, 앱을 게시하기 전에 테스트 광고 단위를 자체 광고 단위 ID로 바꿔야 합니다.

플랫폼별 설정

네이티브 광고를 만들려면 iOS 및 Android용 플랫폼별 코드를 작성한 다음 Dart 구현을 수정하여 적용한 네이티브 코드 변경사항을 활용해야 합니다.

Android

플러그인 가져오기

Android에서 Google 모바일 광고 플러그인을 구현하려면 NativeAdFactory API를 구현하는 클래스가 필요합니다. Android 프로젝트에서 이 API를 참조하려면 settings.gradle에 다음 줄을 추가합니다.

def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
    pluginsFile.withInputStream { stream -> plugins.load(stream) }
}

plugins.each { name, path ->
    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
    include ":$name"
    project(":$name").projectDir = pluginDirectory
}

NativeAdFactory 구현

다음으로 NativeAdFactory를 구현하고 createNativeAd() 메서드를 재정의하는 클래스를 만듭니다.

package io.flutter.plugins.googlemobileadsexample;

import android.graphics.Color;
import android.view.LayoutInflater;
import android.widget.TextView;
import com.google.android.gms.ads.nativead.NativeAd;
import com.google.android.gms.ads.nativead.NativeAdView;
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin.NativeAdFactory;
import java.util.Map;

/**
 * my_native_ad.xml can be found at
 * github.com/googleads/googleads-mobile-flutter/blob/main/packages/google_mobile_ads/
 *     example/android/app/src/main/res/layout/my_native_ad.xml
 */
class NativeAdFactoryExample implements NativeAdFactory {
  private final LayoutInflater layoutInflater;

  NativeAdFactoryExample(LayoutInflater layoutInflater) {
    this.layoutInflater = layoutInflater;
  }

  @Override
  public NativeAdView createNativeAd(
      NativeAd nativeAd, Map<String, Object> customOptions) {
    final NativeAdView adView =
        (NativeAdView) layoutInflater.inflate(R.layout.my_native_ad, null);

    // Set the media view.
    adView.setMediaView((MediaView) adView.findViewById(R.id.ad_media));

    // Set other ad assets.
    adView.setHeadlineView(adView.findViewById(R.id.ad_headline));
    adView.setBodyView(adView.findViewById(R.id.ad_body));
    adView.setCallToActionView(adView.findViewById(R.id.ad_call_to_action));
    adView.setIconView(adView.findViewById(R.id.ad_app_icon));
    adView.setPriceView(adView.findViewById(R.id.ad_price));
    adView.setStarRatingView(adView.findViewById(R.id.ad_stars));
    adView.setStoreView(adView.findViewById(R.id.ad_store));
    adView.setAdvertiserView(adView.findViewById(R.id.ad_advertiser));

    // The headline and mediaContent are guaranteed to be in every NativeAd.
    ((TextView) adView.getHeadlineView()).setText(nativeAd.getHeadline());
    adView.getMediaView().setMediaContent(nativeAd.getMediaContent());

    // These assets aren't guaranteed to be in every NativeAd, so it's important to
    // check before trying to display them.
    if (nativeAd.getBody() == null) {
      adView.getBodyView().setVisibility(View.INVISIBLE);
    } else {
      adView.getBodyView().setVisibility(View.VISIBLE);
      ((TextView) adView.getBodyView()).setText(nativeAd.getBody());
    }

    if (nativeAd.getCallToAction() == null) {
      adView.getCallToActionView().setVisibility(View.INVISIBLE);
    } else {
      adView.getCallToActionView().setVisibility(View.VISIBLE);
      ((Button) adView.getCallToActionView()).setText(nativeAd.getCallToAction());
    }

    if (nativeAd.getIcon() == null) {
      adView.getIconView().setVisibility(View.GONE);
    } else {
      ((ImageView) adView.getIconView()).setImageDrawable(nativeAd.getIcon().getDrawable());
      adView.getIconView().setVisibility(View.VISIBLE);
    }

    if (nativeAd.getPrice() == null) {
      adView.getPriceView().setVisibility(View.INVISIBLE);
    } else {
      adView.getPriceView().setVisibility(View.VISIBLE);
      ((TextView) adView.getPriceView()).setText(nativeAd.getPrice());
    }

    if (nativeAd.getStore() == null) {
      adView.getStoreView().setVisibility(View.INVISIBLE);
    } else {
      adView.getStoreView().setVisibility(View.VISIBLE);
      ((TextView) adView.getStoreView()).setText(nativeAd.getStore());
    }

    if (nativeAd.getStarRating() == null) {
      adView.getStarRatingView().setVisibility(View.INVISIBLE);
    } else {
      ((RatingBar) adView.getStarRatingView()).setRating(nativeAd.getStarRating()
          .floatValue());
      adView.getStarRatingView().setVisibility(View.VISIBLE);
    }

    if (nativeAd.getAdvertiser() == null) {
      adView.getAdvertiserView().setVisibility(View.INVISIBLE);
    } else {
      adView.getAdvertiserView().setVisibility(View.VISIBLE);
      ((TextView) adView.getAdvertiserView()).setText(nativeAd.getAdvertiser());
    }

    // This method tells the Google Mobile Ads SDK that you have finished populating your
    // native ad view with this native ad.
    adView.setNativeAd(nativeAd);

    return adView;
  }
}

NativeAdView 레이아웃 구성의 예는 my_native_ad.xml을 참고하세요.

NativeAdFactory 등록

NativeAdFactory 구현은 MainActivity.configureFlutterEngine(FlutterEngine)를 호출할 때 고유한 String 식별자인 factoryId에 등록해야 합니다. factoryId는 나중에 Dart 코드에서 네이티브 광고를 인스턴스화할 때 사용됩니다.

앱에서 사용하는 고유한 네이티브 광고 레이아웃 각각에 NativeAdFactory를 구현 및 등록할 수도 있고 단일 레이아웃으로 모든 레이아웃을 처리할 수도 있습니다.

add-to-app을 사용하여 빌드할 때는 cleanUpFlutterEngine(engine)에서 NativeAdFactory의 등록도 취소해야 합니다.

NativeAdFactoryExample를 만든 후 다음과 같이 MainActivity를 설정합니다.

package my.app.path;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin;

public class MainActivity extends FlutterActivity {
  @Override
  public void configureFlutterEngine(FlutterEngine flutterEngine) {
    flutterEngine.getPlugins().add(new GoogleMobileAdsPlugin());
    super.configureFlutterEngine(flutterEngine);

    GoogleMobileAdsPlugin.registerNativeAdFactory(flutterEngine,
        "adFactoryExample", NativeAdFactoryExample());
  }

  @Override
  public void cleanUpFlutterEngine(FlutterEngine flutterEngine) {
    GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, "adFactoryExample");
  }
}

iOS

NativeAdFactory 구현

iOS에서 Google 모바일 광고 플러그인을 구현하려면 FLTNativeAdFactory API를 구현하는 클래스가 필요합니다. NativeAdFactory를 구현하는 클래스를 만들고 createNativeAd() 메서드를 구현합니다.

#import "FLTGoogleMobileAdsPlugin.h"

/**
 * The example NativeAdView.xib can be found at
 * github.com/googleads/googleads-mobile-flutter/blob/main/packages/google_mobile_ads/
 *     example/ios/Runner/NativeAdView.xib
 */
@interface NativeAdFactoryExample : NSObject <FLTNativeAdFactory>
@end

@implementation NativeAdFactoryExample
- (GADNativeAdView *)createNativeAd:(GADNativeAd *)nativeAd
                      customOptions:(NSDictionary *)customOptions {
  // Create and place the ad in the view hierarchy.
  GADNativeAdView *adView =
      [[NSBundle mainBundle] loadNibNamed:@"NativeAdView" owner:nil options:nil].firstObject;

  // Populate the native ad view with the native ad assets.
  // The headline is guaranteed to be present in every native ad.
  ((UILabel *)adView.headlineView).text = nativeAd.headline;

  // These assets are not guaranteed to be present. Check that they are before
  // showing or hiding them.
  ((UILabel *)adView.bodyView).text = nativeAd.body;
  adView.bodyView.hidden = nativeAd.body ? NO : YES;

  [((UIButton *)adView.callToActionView) setTitle:nativeAd.callToAction
                                         forState:UIControlStateNormal];
  adView.callToActionView.hidden = nativeAd.callToAction ? NO : YES;

  ((UIImageView *)adView.iconView).image = nativeAd.icon.image;
  adView.iconView.hidden = nativeAd.icon ? NO : YES;

  ((UILabel *)adView.storeView).text = nativeAd.store;
  adView.storeView.hidden = nativeAd.store ? NO : YES;

  ((UILabel *)adView.priceView).text = nativeAd.price;
  adView.priceView.hidden = nativeAd.price ? NO : YES;

  ((UILabel *)adView.advertiserView).text = nativeAd.advertiser;
  adView.advertiserView.hidden = nativeAd.advertiser ? NO : YES;

  // In order for the SDK to process touch events properly, user interaction
  // should be disabled.
  adView.callToActionView.userInteractionEnabled = NO;

  // Associate the native ad view with the native ad object. This is
  // required to make the ad clickable.
  // Note: this should always be done after populating the ad views.
  adView.nativeAd = nativeAd;

  return adView;
}
@end

GADNativeAdView 레이아웃 구성의 예는 NativeAdView.xib을 참고하세요.

NativeAdFactory 등록

FLTNativeAdFactoryregisterNativeAdFactory:factoryId:nativeAdFactory:의 고유 String 식별자 factoryId에 등록해야 합니다. factoryId는 나중에 Dart 코드에서 네이티브 광고를 인스턴스화할 때 사용됩니다.

앱에서 사용하는 고유한 네이티브 광고 레이아웃 각각에 FLTNativeAdFactory를 구현 및 등록할 수도 있고, 단일 로직으로 모든 레이아웃을 처리할 수도 있습니다.

FLTNativeAdFactory를 만든 후 다음과 같이 AppDelegate를 설정합니다.

#import "FLTGoogleMobileAdsPlugin.h"
#import "NativeAdFactoryExample.h"

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
      didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];

  // Must be added after GeneratedPluginRegistrant registerWithRegistry:self];
  // is called.
  NativeAdFactoryExample *nativeAdFactory = [[NativeAdFactoryExample alloc] init];
  [FLTGoogleMobileAdsPlugin registerNativeAdFactory:self
                                          factoryId:@"adFactoryExample"
                                    nativeAdFactory:nativeAdFactory];

  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

광고 로드

플랫폼별 코드를 추가한 후 Dart로 전환하여 광고를 로드합니다. factoryID가 이전에 등록한 ID와 일치하는지 확인합니다.

class NativeExampleState extends State<NativeExample> {
  NativeAd? _nativeAd;
  bool _nativeAdIsLoaded = false;

 // TODO: replace this test ad unit with your own ad unit.
 final String _adUnitId = Platform.isAndroid
      ? 'ca-app-pub-3940256099942544/2247696110'
      : 'ca-app-pub-3940256099942544/3986624511';

  /// Loads a native ad.
  void loadAd() {
    _nativeAd = NativeAd(
        adUnitId: _adUnitId,
        // Factory ID registered by your native ad factory implementation.
        factoryId: 'adFactoryExample',
        listener: NativeAdListener(
          onAdLoaded: (ad) {
            print('$NativeAd loaded.');
            setState(() {
              _nativeAdIsLoaded = true;
            });
          },
          onAdFailedToLoad: (ad, error) {
            // Dispose the ad here to free resources.
            print('$NativeAd failedToLoad: $error');
            ad.dispose();
          },
        ),
        request: const AdRequest(),
        // Optional: Pass custom options to your native ad factory implementation.
        customOptions: {'custom-option-1', 'custom-value-1'}
    );
    _nativeAd.load();
  }
}

네이티브 광고 이벤트

네이티브 광고 상호작용과 관련된 이벤트에 대한 알림을 받으려면 광고의 listener 속성을 사용하세요. 그런 다음 NativeAdListener를 구현하여 광고 이벤트 콜백을 수신합니다.

class NativeExampleState extends State<NativeExample> {
  NativeAd? _nativeAd;
  bool _nativeAdIsLoaded = false;

 // TODO: replace this test ad unit with your own ad unit.
 final String _adUnitId = Platform.isAndroid
      ? 'ca-app-pub-3940256099942544/2247696110'
      : 'ca-app-pub-3940256099942544/3986624511';

  /// Loads a native ad.
  void loadAd() {
    _nativeAd = NativeAd(
        adUnitId: _adUnitId,
        // Factory ID registered by your native ad factory implementation.
        factoryId: 'adFactoryExample',
        listener: NativeAdListener(
          onAdLoaded: (ad) {
            print('$NativeAd loaded.');
            setState(() {
              _nativeAdIsLoaded = true;
            });
          },
          onAdFailedToLoad: (ad, error) {
            // Dispose the ad here to free resources.
            print('$NativeAd failedToLoad: $error');
            ad.dispose();
          },
          // Called when a click is recorded for a NativeAd.
          onAdClicked: (ad) {},
          // Called when an impression occurs on the ad.
          onAdImpression: (ad) {},
          // Called when an ad removes an overlay that covers the screen.
          onAdClosed: (ad) {},
          // Called when an ad opens an overlay that covers the screen.
          onAdOpened: (ad) {},
          // For iOS only. Called before dismissing a full screen view
          onAdWillDismissScreen: (ad) {},
          // Called when an ad receives revenue value.
          onPaidEvent: (ad, valueMicros, precision, currencyCode) {},
        ),
        request: const AdRequest(),
        // Optional: Pass custom options to your native ad factory implementation.
        customOptions: {'custom-option-1', 'custom-value-1'}
    );
    _nativeAd.load();
        
  }
}

디스플레이 광고

NativeAd를 위젯으로 표시하려면 load()를 호출한 후 지원되는 광고를 사용하여 AdWidget를 인스턴스화해야 합니다. load()를 호출하기 전에 위젯을 만들 수 있지만 위젯 트리에 위젯을 추가하기 전에 load()를 호출해야 합니다.

AdWidget는 Flutter의 Widget 클래스에서 상속되며 다른 위젯처럼 사용할 수 있습니다. iOS에서는 지정된 너비와 높이를 바탕으로 컨테이너에 위젯을 배치해야 합니다. 그렇지 않으면 광고가 게재되지 않을 수 있습니다.

final Container adContainer = Container(
  alignment: Alignment.center,
  child: AdWidget adWidget = AdWidget(ad: _nativeAd!),
  width: WIDTH,
  height: HEIGHT,
);

광고 폐기

NativeAd에 더 이상 액세스할 필요가 없으면 폐기해야 합니다. dispose()를 호출하기 가장 좋은 시점은 네이티브 광고와 연결된 AdWidget가 위젯 트리 및 AdListener.onAdFailedToLoad() 콜백에서 삭제된 후입니다.

다음 단계

GitHub의 전체 예

네이티브 플랫폼