Flutter 앱에 AdMob 배너 및 네이티브 인라인 광고 추가

이 Codelab에서는 Flutter 앱에 AdMob 배너 및 AdMob 네이티브 인라인 광고를 구현합니다.

빌드할 항목

이 Codelab은 Flutter용 Google 모바일 광고 플러그인을 사용하여 Flutter 앱에 AdMob 인라인 배너 및 AdMob 네이티브 인라인 광고를 구현하는 방법을 안내합니다.

이 Codelab을 진행하는 동안 코드 버그, 문법 오류, 불명확한 문구 등의 문제가 발생하면 Codelab 왼쪽 하단에 있는 오류 신고 링크를 통해 신고해 주세요.

학습 내용

  • Google 모바일 광고 Flutter 플러그인 구성 방법
  • Flutter 앱의 인라인 배너 및 보상형 광고 구현 방법

필요한 항목

  • Android 스튜디오 4.1 이상
  • Xcode 12 이상(iOS 개발용)

AdMob 사용 경험 수준을 평가해 주세요.

초급 중급 고급

Flutter 사용 경험 수준을 평가해 주세요.

초급 중급 고급

Flutter 개발 환경 설정

  1. 머신에 Flutter SDK를 설치합니다.
  2. Android 스튜디오를 열고 Android Studio용 Flutter 확장 프로그램을 설치합니다.

코드 다운로드

소스 코드 다운로드 GitHub에서 저장소 보기

zip 파일을 다운로드한 후 콘텐츠의 압축을 풉니다. 이름이 admob-inline-ads-in-flutter-main인 폴더가 생성됩니다.

또는 명령줄에서 GitHub 저장소를 클론할 수 있습니다.

$ git clone https://github.com/googlecodelabs/admob-inline-ads-in-flutter

저장소에는 다음과 같은 폴더 세 개가 있습니다.

  • android_studio_folder.png starter: 이 Codelab에서 빌드할 시작 코드입니다.
  • android_studio_folder.png complete: 이 Codelab의 완성된 코드입니다. (네이티브 코드의 경우 자바 및 Objective-C)
  • android_studio_folder.png complete_kotlin_swift: 이 Codelab의 완성된 코드입니다. (네이티브 코드의 경우 Kotlin 및 Swift)

Flutter는 멀티 플랫폼 SDK이므로 AdMob에서 Android와 iOS 모두에 사용할 앱과 광고 단위를 추가해야 합니다.

Android 설정

Android에 맞춰 설정하려면 Android 앱을 추가하고 광고 단위를 만들어야 합니다.

Android 앱 추가

  1. AdMob 콘솔 메뉴에서 앱 추가를 클릭합니다.
  2. 앱을 Google Play 또는 App Store에 게시하셨나요?라는 메시지가 표시되면 아니요를 클릭합니다.
  3. 앱 이름 필드에 AdMob inline ads를 입력하고 플랫폼으로 Android를 선택합니다.

e4d968797610c8c0.png

  1. 이 Codelab을 완료하기 위해 사용자 측정항목을 사용할 필요는 없습니다. 하지만 사용자 행동을 더 자세히 이해할 수 있으므로 사용하는 것이 좋습니다. 추가를 클릭하여 과정을 완료합니다.

5204925f5c652b41.png

광고 단위 만들기

광고 단위를 추가하려면 다음 단계를 따릅니다.

  1. AdMob 콘솔의 메뉴에서 AdMob 인라인 광고 앱을 선택합니다.
  2. 광고 단위 메뉴를 클릭합니다.

배너

  1. 광고 단위 추가를 클릭합니다.
  2. 형식으로 배너를 선택합니다.
  3. 광고 단위 이름 필드에 android-inline-banner를 입력합니다.
  4. 광고 단위 만들기를 클릭하여 과정을 완료합니다.

네이티브

  1. 광고 단위 추가를 클릭합니다.
  2. 형식으로 네이티브 광고 고급형을 선택합니다.
  3. 광고 단위 이름 필드에 android-inline-native를 입력합니다.
  4. 광고 단위 만들기를 클릭하여 과정을 완료합니다.

새 광고 단위에서 광고를 게재할 수 있으려면 보통 몇 시간이 걸립니다.

광고 동작을 즉시 테스트하려면 Android 앱 ID/광고 단위 ID 표에 나와 있는 테스트 앱 ID와 광고 단위 ID를 사용하세요.

iOS 설정

iOS에 맞춰 설정하려면 iOS 앱을 추가하고 광고 단위를 만들어야 합니다.

iOS 앱 추가

  1. AdMob 콘솔 메뉴에서 앱 추가를 클릭합니다.
  2. 앱을 Google Play 또는 App Store에 게시하셨나요?라는 메시지가 표시되면 아니요를 클릭합니다.
  3. 앱 이름 필드에 AdMob inline ads를 입력하고 플랫폼으로 iOS를 선택합니다.

e00fc8ac930a251b.png

  1. 이 Codelab을 완료하기 위해 사용자 측정항목을 사용할 필요는 없습니다. 하지만 사용자 행동을 더 자세히 이해할 수 있으므로 사용하는 것이 좋습니다. 추가를 클릭하여 과정을 완료합니다.

5204925f5c652b41.png

광고 단위 만들기

광고 단위를 추가하려면 다음 단계를 따릅니다.

  1. AdMob 콘솔의 메뉴에서 AdMob 인라인 광고 앱을 선택합니다.
  2. 광고 단위 메뉴를 클릭합니다.

배너

  1. 광고 단위 추가를 클릭합니다.
  2. 형식으로 배너를 선택합니다.
  3. 광고 단위 이름 필드에 ios-inline-banner를 입력합니다.
  4. 광고 단위 만들기를 클릭하여 과정을 완료합니다.

네이티브

  1. 광고 단위 추가를 클릭합니다.
  2. 형식으로 네이티브 광고 고급형을 선택합니다.
  3. 광고 단위 이름 필드에 ios-inline-native를 입력합니다.
  4. 광고 단위 만들기를 클릭하여 과정을 완료합니다.

새 광고 단위에서 광고를 게재할 수 있으려면 보통 몇 시간이 걸립니다.

광고의 동작을 즉시 테스트하려면 다음 표에 나와 있는 테스트 앱 ID와 광고 단위 ID를 사용하세요.

선택사항: 테스트 AdMob 앱 및 광고 단위 사용

새 애플리케이션과 광고 단위를 직접 만드는 대신 Codelab을 따르려면 다음 표에 나온 테스트 AdMob 앱 ID와 광고 단위 ID를 사용하세요.

Android 앱 ID/광고 단위 ID

항목

앱 ID/광고 단위 ID

AdMob 앱 ID

ca-app-pub-3940256099942544~3347511713

배너

ca-app-pub-3940256099942544/6300978111

네이티브

ca-app-pub-3940256099942544/2247696110

iOS 앱 ID/광고 단위 ID

항목

앱 ID/광고 단위 ID

AdMob 앱 ID

ca-app-pub-3940256099942544~1458002511

배너

ca-app-pub-3940256099942544/2934735716

네이티브

ca-app-pub-3940256099942544/3986624511

테스트 광고에 대한 자세한 내용은 Android 테스트 광고iOS 테스트 광고 개발자 설명서를 참고하세요.

Flutter는 플러그인을 사용하여 다양한 플랫폼별 서비스에 대한 액세스를 제공합니다. 플러그인을 사용하면 각 플랫폼에서 서비스와 API에 액세스할 수 있습니다.

google_mobile_ads 플러그인은 AdMob API를 사용하여 배너, 전면 광고, 보상형 광고, 네이티브 광고의 로드 및 표시를 지원합니다.

Flutter는 멀티 플랫폼 SDK이므로 google_mobile_ads 플러그인을 iOS와 Android에 모두 적용할 수 있습니다. 따라서 플러그인을 Flutter 앱에 추가하면 AdMob 인라인 광고 앱의 Android 및 iOS 버전 모두에 사용됩니다.

Google 모바일 광고 플러그인을 종속 항목으로 추가

AdMob 인라인 광고 프로젝트에서 AdMob API에 액세스하려면 google_mobile_ads를 프로젝트의 루트에 있는 pubspec.yaml 파일에 종속 항목으로 추가합니다.

pubspec.yaml

...
environment:
  # TODO: Update the minimum sdk version to 2.12.0 to support null safety.
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  google_fonts: ^0.3.9

  # TODO: Add google_mobile_ads as a dependency
  google_mobile_ads: ^0.12.0

...

Pub get(Pub 받기)을 클릭하여 AdMob 인라인 광고 프로젝트에 플러그인을 설치합니다.

39e66c2a3b7f078b.png

AndroidManifest.xml 업데이트(Android)

  1. Android 스튜디오에서 android/app/src/main/AndroidManifest.xml 파일을 엽니다.
  2. 이름이 com.google.android.gms.ads.APPLICATION_ID<meta-data> 태그를 추가하여 AdMob 앱 ID를 추가합니다. 예를 들어 AdMob 앱 ID가 ca-app-pub-3940256099942544~3347511713인 경우 AndroidManifest.xml 파일에 다음 행을 추가해야 합니다.

AndroidManifest.xml

<manifest>
    ...
    <application>
       ...
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-3940256099942544~3347511713"/>
    </application>

</manifest>

Info.plist 업데이트(iOS)

  1. Android 스튜디오에서 ios/Runner/Info.plist 파일을 엽니다.
  2. AdMob 앱 ID의 문자열 값이 포함된 GADApplicationIdentifier 키를 추가합니다. 예를 들어 AdMob 앱 ID가 ca-app-pub-3940256099942544~1458002511인 경우 Info.plist 파일에 다음 행을 추가해야 합니다.

ios/Runner/Info.plist

...
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>
...

lib 디렉터리에 ad_helper.dart라는 새 파일을 만듭니다. 그런 다음 AdMob 앱 ID와 Android 및 iOS용 광고 단위 ID를 제공하는 AdHelper 클래스를 구현합니다.

AdMob 앱 ID(ca-app-pub-xxxxxx~yyyyy)와 광고 단위 ID(ca-app-pub-xxxxxxx/yyyyyyyy)를 이전 단계에서 만든 ID로 바꿔야 합니다.

ad_helper.dart

import 'dart:io';

class AdHelper {

  static String get bannerAdUnitId {
    if (Platform.isAndroid) {
      return "<YOUR_ANDROID_BANNER_AD_UNIT_ID";
    } else if (Platform.isIOS) {
      return "<YOUR_IOS_BANNER_AD_UNIT_ID>";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }

  static String get nativeAdUnitId {
    if (Platform.isAndroid) {
      return "<YOUR_ANDROID_NATIVE_AD_UNIT_ID>";
    } else if (Platform.isIOS) {
      return "<YOUR_IOS_NATIVE_AD_UNIT_ID>";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }
}

테스트 AdMob 앱 ID와 테스트 광고 단위 ID를 사용하려면 다음 코드 스니펫을 사용합니다.

ad_helper.dart

import 'dart:io';

class AdHelper {

  static String get bannerAdUnitId {
    if (Platform.isAndroid) {
      return 'ca-app-pub-3940256099942544/6300978111';
    } else if (Platform.isIOS) {
      return 'ca-app-pub-3940256099942544/2934735716';
    }
    throw new UnsupportedError("Unsupported platform");
  }

  static String get nativeAdUnitId {
    if (Platform.isAndroid) {
      return 'ca-app-pub-3940256099942544/2247696110';
    } else if (Platform.isIOS) {
      return 'ca-app-pub-3940256099942544/3986624511';
    }
    throw new UnsupportedError("Unsupported platform");
  }
}

광고를 로드하기 전에 Google 모바일 광고 SDK를 초기화해야 합니다. lib/home_page.dart 파일을 열고 _initGoogleMobileAds()를 수정하여 SDK를 초기화한 다음 홈페이지를 로드합니다.

SDK 초기화가 완료된 후에 결과를 가져오려면 _initGoogleMobileAds() 메서드의 반환 유형을 Future<dynamic>에서 Future<InitializationStatus>로 변경해야 합니다.

home_page.dart

// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';

import 'package:flutter/material.dart';

...

class HomePage extends StatelessWidget {

  ...

  Future<InitializationStatus> _initGoogleMobileAds() {
    // TODO: Initialize Google Mobile Ads SDK
    return MobileAds.instance.initialize();
  }
}

이 섹션에서는 다음 스크린샷과 같이 목록 중간에 배너 광고를 표시합니다.

a3cd508128793e41.png

  1. lib/banner_inline_page.dart 파일을 엽니다.
  2. 다음 행을 추가하여 ad_helper.dartgoogle_mobile_ads.dart를 가져옵니다.
...

// TODO: Import ad_helper.dart
import 'package:admob_inline_ads_in_flutter/ad_helper.dart';

// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';

class BannerInlinePage extends StatefulWidget {
  ...
}
  1. _BannerInlinePageState 클래스에서 배너 광고에 다음 멤버와 메서드를 추가합니다.

_kAdIndex는 배너 광고가 표시될 인덱스를 나타내며 _getDestinationItemIndex() 메서드에서 항목 인덱스를 계산하는 데 사용됩니다.

banner_inline_page.dart

class _BannerInlinePageState extends State<BannerInlinePage> {

  // TODO: Add _kAdIndex
  static final _kAdIndex = 4;

  // TODO: Add a BannerAd instance
  late BannerAd _ad;

  // TODO: Add _isAdLoaded
  bool _isAdLoaded = false;

  ...

  // TODO: Add _getDestinationItemIndex()
  int _getDestinationItemIndex(int rawIndex) {
    if (rawIndex >= _kAdIndex && _isAdLoaded) {
      return rawIndex - 1;
    }
    return rawIndex;
  }

  ...
}
  1. initState() 메서드에서 320x50 배너(AdSize.banner)의 BannerAd를 생성하고 로드합니다. 광고 이벤트 리스너는 광고가 로드될 때(MobileAdEvent.loaded) UI(setState())를 업데이트하도록 구성되어 있습니다.

banner_inline_page.dart

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

  // TODO: Create a BannerAd instance
  _ad = BannerAd(
    adUnitId: AdHelper.bannerAdUnitId,
    size: AdSize.banner,
    request: AdRequest(),
    listener: AdListener(
      onAdLoaded: (_) {
        setState(() {
          _isAdLoaded = true;
        });
      },
      onAdFailedToLoad: (ad, error) {
        // Releases an ad resource when it fails to load
        ad.dispose();

        print('Ad load failed (code=${error.code} message=${error.message})');
      },
    ),
  );

  // TODO: Load an ad
  _ad.load();
}
  1. 가능한 경우 배너 광고를 표시하도록 build() 메서드를 수정합니다.
  2. itemCount,를 업데이트하여 배너 광고 항목을 계산하고 itemBuilder,를 업데이트하여 광고가 로드될 때 광고 인덱스(_kAdIndex)에서 배너 광고를 렌더링합니다.
  3. _getDestinationItemIndex() 메서드를 사용하여 콘텐츠 항목의 인덱스를 가져오도록 코드를 업데이트합니다.

banner_inline_page.dart

@override
Widget build(BuildContext context) {
  return Scaffold(
    ...
    body: ListView.builder(
      // TODO: Adjust itemCount based on the ad load state
      itemCount: widget.entries.length + (_isAdLoaded ? 1 : 0),
      itemBuilder: (context, index) {
        // TODO: Render a banner ad
        if (_isAdLoaded && index == _kAdIndex) {
          return Container(
            child: AdWidget(ad: _ad),
            width: _ad.size.width.toDouble(),
            height: 72.0,
            alignment: Alignment.center,
          );
        } else {
          // TODO: Get adjusted item index from _getDestinationItemIndex()
          final item = widget.entries[_getDestinationItemIndex(index)];

          return ListTile(
            ...
          );
        }
      },
    ),
  );
}
  1. dispose() 콜백 메서드에서 BannerAd.dispose() 메서드를 호출하여 BannerAd 객체와 연결된 리소스를 해제합니다.

banner_inline_page.dart

@override
void dispose() {
  // TODO: Dispose a BannerAd object
  _ad.dispose();

  super.dispose();
}

이제 완료됐습니다. 프로젝트를 실행하고 홈페이지에서 Banner inline ad(배너 인라인 광고) 버튼을 클릭합니다. 광고가 로드되면 목록 중간에 배너 광고가 표시됩니다.

f1372268878a80a2.png c32af50872514224.png

이 섹션에서는 다음 스크린샷과 같이 목록 중간에 네이티브 광고를 표시합니다.

fbe594d5d9ce08ea.png

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

그러나 Flutter 위젯을 사용하여 네이티브 UI 구성요소를 직접 생성할 수는 없습니다. 따라서 네이티브 광고 객체(Android의 UnifiedNativeAd, iOS의 GADUnifiedNativeAd)에서 플랫폼별 네이티브 광고 뷰(Android의 UnifiedNativeAdView, iOS의 GADUnifiedNativeAdView)를 빌드하는 데 사용되는 각 플랫폼의 NativeAdFactory를 구현해야 합니다.

Android용 NativeAdFactory 구현(자바)

  1. android/build.gradle 파일 또는 android 폴더의 파일을 열고 Open for Editing in Android Studio(편집을 위해 Android Studio에서 열기)를 클릭하여 Android 프로젝트를 엽니다.

88d3e99426cd8d8c.png

  1. 새 프로젝트를 열 창을 선택하라는 메시지가 표시되면 New Window(새 창)를 클릭하여 Android 프로젝트에서 작업하는 동안 Flutter 프로젝트를 열어 둡니다.

5d554e50a83f2bd1.png

네이티브 광고 레이아웃 만들기

  1. Android 프로젝트를 열고 Android 스튜디오의 프로젝트 창에서 을 마우스 오른쪽 버튼으로 클릭한 다음 컨텍스트 메뉴에서 New > Android Resource File을 선택합니다.

7b2b6d28d0fe80a9.png

  1. New Resource File 대화상자에 list_tile_native_ad.xml을 파일 이름으로 입력합니다.
  2. 리소스 유형으로 Layout을 선택하고 com.google.android.gms.ads.formats.UnifiedNativeAdView를 루트 요소로 입력합니다.
  3. OK를 클릭하여 새 레이아웃 파일을 만듭니다.

615bf550a2ce9c45.png

  1. 다음과 같이 광고 레이아웃을 구현합니다. 레이아웃은 대상 플랫폼 사용자 환경의 시각적 디자인과 일치해야 합니다.

list_tile_native_ad.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.gms.ads.formats.UnifiedNativeAdView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_list_tile_native_ad_attribution_small"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#F19938"
            android:text="Ad"
            android:textColor="#FFFFFF"
            android:textSize="12sp" />

        <ImageView
            android:id="@+id/iv_list_tile_native_ad_icon"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:scaleType="fitXY"
            tools:background="#EDEDED" />

        <TextView
            android:id="@+id/tv_list_tile_native_ad_attribution_large"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:background="#F19938"
            android:gravity="center"
            android:text="Ad"
            android:textColor="#FFFFFF"
            android:visibility="invisible" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="80dp"
            android:layout_marginLeft="80dp"
            android:layout_marginEnd="16dp"
            android:layout_marginRight="16dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_list_tile_native_ad_headline"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:lines="1"
                android:maxLines="1"
                android:textColor="#000000"
                android:textSize="16sp"
                tools:text="Headline" />

            <TextView
                android:id="@+id/tv_list_tile_native_ad_body"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:lines="1"
                android:maxLines="1"
                android:textColor="#828282"
                android:textSize="14sp"
                tools:text="body" />

        </LinearLayout>

    </FrameLayout>

</com.google.android.gms.ads.formats.UnifiedNativeAdView>

ListTileNativeAdFactory 클래스 만들기

  1. 프로젝트 창에서 com.codelab.flutter.admobinlineads 패키지를 마우스 오른쪽 버튼으로 클릭하고 New > Java Class를 선택합니다.

609df24b6a0b647e.png

  1. ListTileNativeAdFactory를 이름으로 입력하고 목록에서 Class를 선택합니다.

642050a1a83d5033.png

  1. New Class 대화상자가 나타나면 모든 항목을 비워 두고 OK를 클릭합니다.

com.codelab.flutter.admobinlineads 패키지에 ListTileNativeAdFactory 클래스가 생성되었음을 확인할 수 있습니다.

3e7c27d864686efa.png

  1. 다음과 같이 ListTileNativeAdFactory 클래스를 구현합니다. 이 클래스는 GoogleMobileAdsPlugin.NativeAdFactory 인터페이스에서 createNativeAd() 메서드를 구현합니다.

Factory 클래스는 네이티브 광고를 렌더링하기 위한 뷰 객체를 생성합니다. 코드에서 알 수 있듯이 Factory 클래스는 UnitifedNativeAdView를 생성하고 UnifiedNativeAd 객체로 채웁니다.

ListTileNativeAdFactory.java

// TODO: Implement ListTileNativeAdFactory class

package com.codelab.flutter.admobinlineads;

import com.google.android.gms.ads.formats.NativeAd;
import com.google.android.gms.ads.formats.UnifiedNativeAd;
import com.google.android.gms.ads.formats.UnifiedNativeAdView;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.Map;

import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin;

class ListTileNativeAdFactory implements GoogleMobileAdsPlugin.NativeAdFactory {

    private final Context context;

    ListTileNativeAdFactory(Context context) {
        this.context = context;
    }

    @Override
    public UnifiedNativeAdView createNativeAd(
            UnifiedNativeAd nativeAd, Map<String, Object> customOptions) {
        UnifiedNativeAdView nativeAdView = (UnifiedNativeAdView) LayoutInflater.from(context)
                .inflate(R.layout.list_tile_native_ad, null);

        TextView attributionViewSmall = nativeAdView
                .findViewById(R.id.tv_list_tile_native_ad_attribution_small);
        TextView attributionViewLarge = nativeAdView
                .findViewById(R.id.tv_list_tile_native_ad_attribution_large);

        ImageView iconView = nativeAdView.findViewById(R.id.iv_list_tile_native_ad_icon);
        NativeAd.Image icon = nativeAd.getIcon();
        if (icon != null) {
            attributionViewSmall.setVisibility(View.VISIBLE);
            attributionViewLarge.setVisibility(View.INVISIBLE);
            iconView.setImageDrawable(icon.getDrawable());
        } else {
            attributionViewSmall.setVisibility(View.INVISIBLE);
            attributionViewLarge.setVisibility(View.VISIBLE);
        }
        nativeAdView.setIconView(iconView);

        TextView headlineView = nativeAdView.findViewById(R.id.tv_list_tile_native_ad_headline);
        headlineView.setText(nativeAd.getHeadline());
        nativeAdView.setHeadlineView(headlineView);

        TextView bodyView = nativeAdView.findViewById(R.id.tv_list_tile_native_ad_body);
        bodyView.setText(nativeAd.getBody());
        bodyView.setVisibility(nativeAd.getBody() != null ? View.VISIBLE : View.INVISIBLE);
        nativeAdView.setBodyView(bodyView);

        nativeAdView.setNativeAd(nativeAd);

        return nativeAdView;
    }
}

ListTileNativeAdFactory 클래스 등록

NativeAdFactory의 인스턴스를 Flutter 측에서 사용하려면 GoogleMobileAdsPlugin에 등록해야 합니다.

  1. MainActivity.java 파일을 열고 configureFlutterEngine() 메서드와 cleanUpFlutterEngine() 메서드를 재정의합니다.
  2. configureFlutterEngine() 메서드에서 고유한 문자열 ID(listTile)를 사용하여 ListTileNativeAdFactory 클래스를 등록합니다.

MainActivity.java

public class MainActivity extends FlutterActivity {

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);

        // TODO: Register the ListTileNativeAdFactory
        GoogleMobileAdsPlugin.registerNativeAdFactory(flutterEngine, "listTile",
                new ListTileNativeAdFactory(getContext()));
    }

    ...
}
  1. 정리 프로세스 중에 모든 NativeAdFactory 인스턴스를 등록 취소해야 합니다. cleanUpFlutterEngine() 메서드에서 ListTileNativeAdFactory 클래스를 등록 취소합니다.

MainActivity.java

public class MainActivity extends FlutterActivity {

    ...

    @Override
    public void cleanUpFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.cleanUpFlutterEngine(flutterEngine);

        // TODO: Unregister the ListTileNativeAdFactory
        GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, "listTile");
    }
}

이제 ListTileNativeAdFactory 클래스를 사용하여 Android에서 네이티브 광고를 렌더링할 준비가 되었습니다.

Android용 NativeAdFactory 구현(Kotlin)

  1. android/build.gradle 파일 또는 android 폴더의 파일을 열고 Open for Editing in Android Studio(편집을 위해 Android Studio에서 열기)를 클릭하여 Android 프로젝트를 엽니다.

88d3e99426cd8d8c.png

  1. 새 프로젝트를 열 창을 선택하라는 메시지가 표시되면 New Window(새 창)를 클릭하여 Android 프로젝트에서 작업하는 동안 Flutter 프로젝트를 열어 둡니다.

5d554e50a83f2bd1.png

네이티브 광고 레이아웃 만들기

  1. Android 프로젝트를 열고 Android 스튜디오의 프로젝트 창에서 을 마우스 오른쪽 버튼으로 클릭한 다음 컨텍스트 메뉴에서 New > Android Resource File을 선택합니다.

7b2b6d28d0fe80a9.png

  1. New Resource File 대화상자에 list_tile_native_ad.xml을 파일 이름으로 입력합니다.
  2. 리소스 유형으로 Layout을 선택하고 com.google.android.gms.ads.formats.UnifiedNativeAdView를 루트 요소로 입력합니다.
  3. OK를 클릭하여 새 레이아웃 파일을 만듭니다.

615bf550a2ce9c45.png

  1. 다음과 같이 광고 레이아웃을 구현합니다. 레이아웃은 대상 플랫폼 사용자 환경의 시각적 디자인과 일치해야 합니다.

list_tile_native_ad.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.gms.ads.formats.UnifiedNativeAdView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_list_tile_native_ad_attribution_small"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#F19938"
            android:text="Ad"
            android:textColor="#FFFFFF"
            android:textSize="12sp" />

        <ImageView
            android:id="@+id/iv_list_tile_native_ad_icon"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:scaleType="fitXY"
            tools:background="#EDEDED" />

        <TextView
            android:id="@+id/tv_list_tile_native_ad_attribution_large"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            android:background="#F19938"
            android:gravity="center"
            android:text="Ad"
            android:textColor="#FFFFFF"
            android:visibility="invisible" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="80dp"
            android:layout_marginLeft="80dp"
            android:layout_marginEnd="16dp"
            android:layout_marginRight="16dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_list_tile_native_ad_headline"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:lines="1"
                android:maxLines="1"
                android:textColor="#000000"
                android:textSize="16sp"
                tools:text="Headline" />

            <TextView
                android:id="@+id/tv_list_tile_native_ad_body"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:lines="1"
                android:maxLines="1"
                android:textColor="#828282"
                android:textSize="14sp"
                tools:text="body" />

        </LinearLayout>

    </FrameLayout>

</com.google.android.gms.ads.formats.UnifiedNativeAdView>

ListTileNativeAdFactory 클래스 만들기

  1. 프로젝트 창에서 com.codelab.flutter.admobinlineads 패키지를 마우스 오른쪽 버튼으로 클릭하고 New > Kotlin File/Class를 선택합니다.

7311744cb97cad75.png

  1. ListTileNativeAdFactory를 이름으로 입력하고 목록에서 Class를 선택합니다.

43a6ac7728826d8.png

  1. com.codelab.flutter.admobinlineads 패키지에 ListTileNativeAdFactory 클래스가 생성되었음을 확인할 수 있습니다.
  2. 다음과 같이 ListTileNativeAdFactory 클래스를 구현합니다. 이 클래스는 GoogleMobileAdsPlugin.NativeAdFactory 인터페이스에서 createNativeAd() 메서드를 구현합니다.

Factory 클래스는 네이티브 광고를 렌더링하기 위한 뷰 객체를 생성합니다. 코드에서 알 수 있듯이 Factory 클래스는 UnitifedNativeAdView를 생성하고 UnifiedNativeAd 객체로 채웁니다.

ListTileNativeAdFactory.kt

// TODO: Implement ListTileNativeAdFactory class

package com.codelab.flutter.admobinlineads

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.google.android.gms.ads.formats.UnifiedNativeAd
import com.google.android.gms.ads.formats.UnifiedNativeAdView
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin

class ListTileNativeAdFactory(val context: Context) : GoogleMobileAdsPlugin.NativeAdFactory {

    override fun createNativeAd(
            nativeAd: UnifiedNativeAd,
            customOptions: MutableMap<String, Any>?
    ): UnifiedNativeAdView {
        val nativeAdView = LayoutInflater.from(context)
                .inflate(R.layout.list_tile_native_ad, null) as UnifiedNativeAdView

        with(nativeAdView) {
            val attributionViewSmall =
                    findViewById<TextView>(R.id.tv_list_tile_native_ad_attribution_small)
            val attributionViewLarge =
                    findViewById<TextView>(R.id.tv_list_tile_native_ad_attribution_large)

            val iconView = findViewById<ImageView>(R.id.iv_list_tile_native_ad_icon)
            val icon = nativeAd.icon
            if (icon != null) {
                attributionViewSmall.visibility = View.VISIBLE
                attributionViewLarge.visibility = View.INVISIBLE
                iconView.setImageDrawable(icon.drawable)
            } else {
                attributionViewSmall.visibility = View.INVISIBLE
                attributionViewLarge.visibility = View.VISIBLE
            }
            this.iconView = iconView

            val headlineView = findViewById<TextView>(R.id.tv_list_tile_native_ad_headline)
            headlineView.text = nativeAd.headline
            this.headlineView = headlineView

            val bodyView = findViewById<TextView>(R.id.tv_list_tile_native_ad_body)
            with(bodyView) {
                text = nativeAd.body
                visibility = if (nativeAd.body.isNotEmpty()) View.VISIBLE else View.INVISIBLE
            }
            this.bodyView = bodyView

            setNativeAd(nativeAd)
        }

        return nativeAdView
    }
}

ListTileNativeAdFactory 클래스 등록

NativeAdFactory의 인스턴스를 Flutter 측에서 사용하려면 GoogleMobileAdsPlugin에 등록해야 합니다.

  1. MainActivity.kt 파일을 열고 configureFlutterEngine() 메서드와 cleanUpFlutterEngine() 메서드를 재정의합니다.
  2. configureFlutterEngine() 메서드에서 고유한 문자열 ID(listTile)를 사용하여 ListTileNativeAdFactory 클래스를 등록합니다.

MainActivity.kt

class MainActivity: FlutterActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        // TODO: Register the ListTileNativeAdFactory
        GoogleMobileAdsPlugin.registerNativeAdFactory(
                flutterEngine, "listTile", ListTileNativeAdFactory(context))
    }

    ...
}
  1. 정리 프로세스 중에 모든 NativeAdFactory 인스턴스를 등록 취소해야 합니다. cleanUpFlutterEngine() 메서드에서 ListTileNativeAdFactory 클래스를 등록 취소합니다.

MainActivity.kt

class MainActivity: FlutterActivity() {
    ...

    override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
        super.cleanUpFlutterEngine(flutterEngine)

        // TODO: Unregister the ListTileNativeAdFactory
        GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, "listTile")
    }
}

이제 ListTileNativeAdFactory 클래스를 사용하여 Android에서 네이티브 광고를 렌더링할 준비가 되었습니다.

iOS용 NativeAdFactory 구현(Objective-C)

ios/Podfile 파일 또는 iOS 폴더의 파일을 열고 Open iOS module in Xcode(Xcode에서 iOS 모듈 열기)를 클릭하여 iOS 프로젝트를 엽니다.

8f8b417705df6741.png

네이티브 광고 레이아웃 준비

네이티브 광고 애셋을 배치하려면 맞춤 뷰(*.xib)가 있어야 합니다. 이 Codelab에서는 사전 구성된 뷰를 사용하여 노력을 최소화할 수 있습니다.

Xcode에서 iOS 프로젝트가 열리면 Runner(러너) 프로젝트에 ListTileNativeAdView.xib가 있는지 확인합니다.

8551d919ccc41818.png

ListTileNativeAdFactory 클래스 만들기

  1. 프로젝트 탐색기에서 Runner(러너) 그룹을 마우스 오른쪽 버튼으로 클릭하고 New File(새 파일)을 선택하여 새 클래스의 헤더 파일을 만듭니다.

249c43a7a1eac87.png

  1. 템플릿 대화상자에서 Header File(헤더 파일)을 선택하고 이름을 ListTileNativeAdFactory로 지정합니다.
  2. ListTileNativeAdFactory.h 파일이 생성되면 다음과 같이 ListNativeAdFactory 클래스를 정의합니다.

ListTileNativeAdFactory.h

#ifndef ListTileNativeAdFactory_h
#define ListTileNativeAdFactory_h

// TODO: Import FLTGoogleMobileAdsPlugin.h
#import "FLTGoogleMobileAdsPlugin.h"

// TODO: Declare ListTileNativeAdFactory
@interface ListTileNativeAdFactory : NSObject<FLTNativeAdFactory>

@end

#endif /* ListTileNativeAdFactory_h */
  1. Runner(러너) 그룹에서 New File(새 파일)을 선택하여 Objective-C 파일을 만듭니다.
  2. 다음 대화상자의 File(파일) 필드에 ListTileNativeAdFactory를 입력하고 파일 형식으로 Empty File(빈 파일)을 선택합니다.

26f03ec58f26d1d2.png

  1. Next(다음)를 클릭하면 새 파일을 만들 폴더를 선택하라는 메시지가 표시됩니다. 아무 것도 변경하지 않고 Create(생성)를 클릭합니다.

c40f7080bdf513ff.png

  1. 다음과 같이 ListTileNativeFactory 클래스를 구현합니다. 이 클래스는 FLTNativeAdFactory 프로토콜에서 createNativeAd() 메서드를 구현합니다.

Factory 클래스는 네이티브 광고를 렌더링하기 위한 뷰 객체를 생성합니다. 코드에서 알 수 있듯이 Factory 클래스는 GADUnitifedNativeAdView를 생성하고 GADUnifiedNativeAd 객체로 채웁니다.

ListTileNativeAdFactory.m

// TODO: Import ListTileNativeAdFactory.h
#import "ListTileNativeAdFactory.h"

// TODO: Implement ListTileNativeAdFactory
@implementation ListTileNativeAdFactory

- (GADUnifiedNativeAdView *)createNativeAd:(GADUnifiedNativeAd *)nativeAd
                             customOptions:(NSDictionary *)customOptions {
  GADUnifiedNativeAdView *nativeAdView =
    [[NSBundle mainBundle] loadNibNamed:@"ListTileNativeAdView" owner:nil options:nil].firstObject;

  ((UILabel *)nativeAdView.headlineView).text = nativeAd.headline;

  ((UILabel *)nativeAdView.bodyView).text = nativeAd.body;
  nativeAdView.bodyView.hidden = nativeAd.body ? NO : YES;

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

  nativeAdView.callToActionView.userInteractionEnabled = NO;

  nativeAdView.nativeAd = nativeAd;

  return nativeAdView;
}

@end

ListTileNativeAdFactory 클래스 등록

FLTNativeAdFactory의 구현을 Flutter 측에서 사용하려면 FLTGoogleMobileAdsPlugin에 등록해야 합니다.

AppDelegate.m 파일을 열고 [FLTGoogleMobileAdsPlugin registerNativeAdFactory] 메서드를 호출하여 ListTileNativeAdFactory를 고유 문자열 ID(listTile)로 등록합니다.

AppDelegate.m

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"

// TODO: Import ListTileNativeAdFactory.h
#import "ListTileNativeAdFactory.h"

@implementation AppDelegate

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

  // TODO: Register ListTileNativeAdFactory
  ListTileNativeAdFactory *listTileFactory = [[ListTileNativeAdFactory alloc] init];
  [FLTGoogleMobileAdsPlugin registerNativeAdFactory:self
                                        factoryId:@"listTile"
                                  nativeAdFactory:listTileFactory];

  // Override point for customization after application launch.
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

이제 ListTileNativeAdFactory를 사용하여 iOS에서 네이티브 광고를 렌더링할 수 있습니다.

iOS용 NativeAdFactory 구현(Swift)

ios/Podfile 파일 또는 iOS 폴더의 파일을 열고 Open iOS module in Xcode(Xcode에서 iOS 모듈 열기)를 클릭하여 iOS 프로젝트를 엽니다.

8f8b417705df6741.png

네이티브 광고 레이아웃 준비

네이티브 광고 애셋을 배치하려면 맞춤 뷰(*.xib)가 있어야 합니다. 이 Codelab에서는 사전 구성된 뷰를 사용하여 노력을 최소화할 수 있습니다.

Xcode에서 iOS 프로젝트가 열리면 Runner(러너) 프로젝트에 ListTileNativeAdView.xib가 있는지 확인합니다.

8551d919ccc41818.png

ListTileNativeAdFactory 클래스 만들기

  1. 프로젝트 탐색기에서 Runner(러너) 그룹을 마우스 오른쪽 버튼으로 클릭하고 New File(새 파일)을 선택하여 새 클래스의 헤더 파일을 만듭니다.

90bdca063c491134.png

  1. 템플릿 대화상자에서 Swift File(Swift 파일)을 선택하고 이름을 ListTileNativeAdFactory로 지정합니다.
  2. ListTileNativeAdFactory.swift 파일이 생성되면 ListNativeAdFactory 클래스를 구현합니다.

이 클래스는 FLTNativeAdFactory 프로토콜에서 createNativeAd() 메서드를 구현합니다.

Factory 클래스는 네이티브 광고를 렌더링하기 위한 뷰 객체를 생성합니다. 코드에서 알 수 있듯이 Factory 클래스는 GADUnitifedNativeAdView를 생성하고 GADUnifiedNativeAd 객체로 채웁니다.

ListTileNativeAdFactory.swift

// TODO: Import google_mobile_ads
import google_mobile_ads

// TODO: Implement ListTileNativeAdFactory
class ListTileNativeAdFactory : FLTNativeAdFactory {

    func createNativeAd(_ nativeAd: GADUnifiedNativeAd,
                        customOptions: [AnyHashable : Any]? = nil) -> GADUnifiedNativeAdView? {
        let nibView = Bundle.main.loadNibNamed("ListTileNativeAdView", owner: nil, options: nil)!.first
        let nativeAdView = nibView as! GADUnifiedNativeAdView

        (nativeAdView.headlineView as! UILabel).text = nativeAd.headline

        (nativeAdView.bodyView as! UILabel).text = nativeAd.body
        nativeAdView.bodyView!.isHidden = nativeAd.body == nil

        (nativeAdView.iconView as! UIImageView).image = nativeAd.icon?.image
        nativeAdView.iconView!.isHidden = nativeAd.icon == nil

        nativeAdView.callToActionView?.isUserInteractionEnabled = false

        nativeAdView.nativeAd = nativeAd

        return nativeAdView
    }
}

ListTileNativeAdFactory 클래스 등록

FLTNativeAdFactory의 구현을 Flutter 측에서 사용하려면 FLTGoogleMobileAdsPlugin에 등록해야 합니다.

AppDelegate.m 파일을 열고 FLTGoogleMobileAdsPlugin.registerNativeAdFactory() 메서드를 호출하여 ListTileNativeAdFactory를 고유 문자열 ID(listTile)로 등록합니다.

AppDelegate.swift

import UIKit
import Flutter

// TODO: Import google_mobile_ads
import google_mobile_ads

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)

    // TODO: Register ListTileNativeAdFactory
    let listTileFactory = ListTileNativeAdFactory()
    FLTGoogleMobileAdsPlugin.registerNativeAdFactory(
        self, factoryId: "listTile", nativeAdFactory: listTileFactory)

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

이제 ListTileNativeAdFactory를 사용하여 iOS에서 네이티브 광고를 렌더링할 수 있습니다.

Flutter 위젯과 네이티브 광고 통합

  1. lib/native_inline_page.dart 파일을 엽니다. 다음 행을 추가하여 ad_helper.dartgoogle_mobile_ads.dart를 가져옵니다.

native_inline_page.dart

...

// TODO: Import ad_helper.dart
import 'package:admob_inline_ads_in_flutter/ad_helper.dart';

// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';

class NativeInlinePage extends StatefulWidget {
  ...
}
  1. _NativeInlinePageState 클래스에서 네이티브 광고에 다음 멤버와 메서드를 추가합니다.

_kAdIndex는 배너 광고가 표시될 인덱스를 나타내며 _getDestinationItemIndex() 메서드에서 항목 인덱스를 계산하는 데 사용됩니다.

native_inline_page.dart

class _NativeInlinePageState extends State<NativeInlinePage> {

  // TODO: Add _kAdIndex
  static final _kAdIndex = 4;

  // TODO: Add a NativeAd instance
  late NativeAd _ad;

  // TODO: Add _isAdLoaded
  bool _isAdLoaded = false;

  ...

  // TODO: Add _getDestinationItemIndex()
  int _getDestinationItemIndex(int rawIndex) {
    if (rawIndex >= _kAdIndex && _isAdLoaded) {
      return rawIndex - 1;
    }
    return rawIndex;
  }

  ...
}
  1. initState() 메서드에서 ListTileNativeAdFactory를 사용하여 NativeAd를 만들고 로드하여 네이티브 광고 뷰를 생성합니다.

플러그인에 Factory를 등록하는 데 사용된 것과 동일한 Factory ID(listTile)가 사용됩니다.

native_inline_page.dart

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

  // TODO: Create a NativeAd instance
  _ad = NativeAd(
    adUnitId: AdHelper.nativeAdUnitId,
    factoryId: 'listTile',
    request: AdRequest(),
    listener: AdListener(
      onAdLoaded: (_) {
        setState(() {
          _isAdLoaded = true;
        });
      },
      onAdFailedToLoad: (ad, error) {
        // Releases an ad resource when it fails to load
        ad.dispose();

        print('Ad load failed (code=${error.code} message=${error.message})');       },
    ),
  );

  _ad.load();
}
  1. 가능한 경우 배너 광고를 표시하도록 build() 메서드를 수정합니다.
  2. itemCount,를 업데이트하여 배너 광고 항목을 계산하고 itemBuilder,를 업데이트하여 광고가 로드되면 광고 인덱스(_kAdIndex)에서 배너 광고를 렌더링합니다.
  3. _getDestinationItemIndex() 메서드를 사용하여 콘텐츠 항목의 인덱스를 가져오도록 코드를 업데이트합니다.

native_inline_page.dart

@override
Widget build(BuildContext context) {
  return Scaffold(
    ...
    body: ListView.builder(
      // TODO: Adjust itemCount based on the ad load state
      itemCount: widget.entries.length + (_isAdLoaded ? 1 : 0),
      itemBuilder: (context, index) {
        // TODO: Render a banner ad
        if (_isAdLoaded && index == _kAdIndex) {
          return Container(
            child: AdWidget(ad: _ad),
            height: 72.0,
            alignment: Alignment.center,
          );
        } else {
          // TODO: Get adjusted item index from _getDestinationItemIndex()
          final item = widget.entries[_getDestinationItemIndex(index)];

          return ListTile(
            ...
          );
        }
      },
    ),
  );
}
  1. dispose() 콜백 메서드에서 NativeAd.dispose() 메서드를 호출하여 NativeAd 객체와 연결된 리소스를 해제합니다.

native_inline_page.dart

@override
void dispose() {
  // TODO: Dispose a NativeAd object
  _ad.dispose();

  super.dispose();
}

이제 완료됐습니다. 프로젝트를 실행하고 홈페이지에서 Native inline ad(네이티브 인라인 광고) 버튼을 클릭합니다. 광고가 로드되면 목록 중간에 네이티브 광고가 표시됩니다.

fbe594d5d9ce08ea.png 5ead873222c800eb.png

Codelab을 완료했습니다. 이 Codelab의 완성된 코드는 android_studio_folder.pngcomplete 또는 android_studio_folder.png complete_kotlin_swift 폴더에서 찾을 수 있습니다.