1. 소개
빌드할 내용
이 Codelab에서는 플레이어가 그림의 이름을 추측할 수 있는 게임인 Awesome Drawing Quiz라고 하는 앱에 배너 광고, 전면 광고 및 보상형 광고를 추가하는 방법을 안내합니다.
| 
 | 
 | 
이 Codelab을 진행하는 동안 문제(코드 버그, 문법 오류, 불분명한 문구)가 발생하는 경우 Codelab 왼쪽 하단에 있는 Report a mistake 링크를 통해 문제를 신고해 주세요.
과정 내용
- Google 모바일 광고 AdMob 플러그인을 구성하는 방법
- Flutter 앱에서 배너 광고, 전면 광고 및 보상형 광고를 구현하는 방법
필요한 사항
- Android 스튜디오 4.1 이상
- Xcode 12 이상(iOS 개발용)
고객님의 AdMob 이용 경험 수준을 어떻게 평가하시겠어요?
고객님의 Flutter 이용 경험 수준을 어떻게 평가하시겠어요?
2. Flutter 개발 환경 설정
이 실습을 완료하려면 Flutter SDK 및 편집기라는 두 가지 소프트웨어가 필요합니다.
다음 기기 중 하나를 사용하여 이 Codelab을 실행할 수 있습니다.
- 컴퓨터에 연결되어 있으며 개발자 모드로 설정된 실제 Android 또는 iOS 기기
- iOS 시뮬레이터(Xcode 도구 설치 필요)
- Android Emulator(Android 스튜디오 설정 필요)
- 브라우저(디버깅 시 Chrome 필요)
- Windows, Linux 또는 macOS 데스크톱 애플리케이션. 배포에 사용할 플랫폼에서 개발해야 합니다. 따라서 Windows 데스크톱 앱을 개발하려면 적절한 빌드 체인에 액세스할 수 있도록 Windows에서 개발해야 합니다. docs.flutter.dev/desktop에 운영체제별 요구사항이 자세히 설명되어 있습니다.
코드 다운로드
zip 파일을 다운로드한 후 콘텐츠를 추출하세요. admob-ads-in-flutter-master라는 폴더가 생성됩니다.
또는 명령줄에서 GitHub 저장소를 클론할 수 있습니다.
$ git clone https://github.com/googlecodelabs/admob-ads-in-flutter
저장소에는 다음 두 폴더가 포함됩니다.
 starter — 이 Codelab에서 빌드할 시작 코드 starter — 이 Codelab에서 빌드할 시작 코드
 complete — 이 Codelab을 위해 완료된 코드 complete — 이 Codelab을 위해 완료된 코드
3. AdMob 앱 및 광고 단위 설정
Flutter는 멀티 플랫폼 SDK이므로 AdMob에서 Android 및 iOS 모두를 위한 앱과 광고 단위를 추가해야 합니다.
Android용으로 설정
Android용으로 설정하려면 Android 앱을 추가하고 광고 단위를 만들어야 합니다.
Android 앱 추가
- AdMob 콘솔의 앱 메뉴에서 앱 추가를 클릭합니다.
- 앱을 Google Play 또는 App Store에 게시하셨나요?라는 질문이 표시되면 아니요를 클릭합니다.
- 앱 이름 입력란에 Awesome Drawing Quiz를 입력하고 Android를 플랫폼으로 선택합니다.

- 이 Codelab을 완료하기 위해 사용자 측정항목을 사용 설정하지 않아도 됩니다. 하지만 그렇게 하면 사용자 행동을 더 자세히 파악할 수 있습니다. 추가를 클릭하여 절차를 완료합니다.

광고 단위 만들기
AdMob에 광고 단위를 추가하는 방법은 다음과 같습니다.
- AdMob 콘솔의 앱 메뉴에서 Awesome Drawing Quiz를 선택합니다.
- 광고 단위 메뉴를 클릭합니다.
배너 광고
| 
 | 
 | 
전면 광고
| 
 | 
 | 
보상형 광고
| 
 | 
 | 
일반적으로 새 광고 단위에서 광고를 게재하는 데는 몇 시간이 소요됩니다.
광고의 작동 방식을 즉시 테스트하려면 Android 앱 ID/광고 단위 ID 및 iOS 앱 ID/광고 단위 ID 표에 표시된 테스트 앱 ID 및 광고 단위 ID를 사용하세요.
iOS용으로 설정
iOS용으로 설정하려면 iOS 앱을 추가하고 광고 단위를 만들어야 합니다.
iOS 앱 추가
- AdMob 콘솔의 앱 메뉴에서 앱 추가를 클릭합니다.
- 앱을 Google Play 또는 App Store에 게시하셨나요?라는 질문이 표시되면 아니요를 클릭합니다.
- 앱 이름 입력란에 Awesome Drawing Quiz를 입력하고 iOS를 플랫폼으로 선택합니다.

- 이 Codelab을 완료하기 위해 사용자 측정항목을 사용 설정하지 않아도 됩니다. 하지만 그렇게 하면 사용자 행동을 더 자세히 파악할 수 있습니다. 추가를 클릭하여 절차를 완료합니다.

광고 단위 만들기
광고 단위를 추가하는 방법은 다음과 같습니다.
- AdMob 콘솔의 앱 메뉴에서 Awesome Drawing Quiz 앱을 선택합니다.
- 광고 단위 메뉴를 클릭합니다.
배너 광고
| 
 | 
 | 
전면 광고
| 
 | 
 | 
보상형 광고
| 
 | 
 | 
일반적으로 새 광고 단위에서 광고를 게재하는 데는 몇 시간이 소요됩니다.
광고의 작동 방식을 즉시 테스트하려면 다음 표에 표시된 테스트 앱 ID 및 광고 단위 ID를 사용하세요.
선택사항: 테스트 AdMob 앱 및 광고 단위 사용
새 애플리케이션과 광고 단위를 직접 만드는 대신 Codelab을 따르려면 다음 표에 나와 있는 테스트 AdMob 앱 ID와 광고 단위 ID를 사용하면 됩니다.
Android 앱 ID/광고 단위 ID
| 항목 | 앱 ID/광고 단위 ID | 
| AdMob 앱 ID | 
 | 
| 배너 광고 | 
 | 
| 전면 광고 | 
 | 
| 보상형 광고 | 
 | 
iOS 앱 ID/광고 단위 ID
| 항목 | 앱 ID/광고 단위 ID | 
| AdMob 앱 ID | 
 | 
| 배너 광고 | 
 | 
| 전면 광고 | 
 | 
| 보상형 광고 | 
 | 
테스트 광고에 대한 자세한 내용은 Android 테스트 광고 및 iOS 테스트 광고 개발자 문서를 참고하세요.
4. Google 모바일 광고 Flutter 플러그인 추가
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.17.0 <3.0.0"
dependencies:
  flutter:
    sdk: flutter
  google_fonts: ^3.0.1
  # TODO: Add google_mobile_ads as a dependency
  google_mobile_ads: ^1.2.0
...
Pub get을 클릭하여 Awesome Drawing Quiz 프로젝트에 플러그인을 설치합니다.

AndroidManifest.xml 업데이트(Android)
- Android 스튜디오에서 android/app/src/main/AndroidManifest.xml파일을 엽니다.
- 이름이 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)
- Android 스튜디오에서 ios/Runner/Info.plist파일을 엽니다.
- AdMob 앱의 문자열 값이 포함된 GADApplicationIdentifier키를 추가합니다. 예를 들어 AdMob 앱 ID가ca-app-pub-3940256099942544~1458002511인 경우Info.plist파일에 다음 행을 추가해야 합니다.
ios/Runner/Info.plist
...
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>
...
5. 광고의 도우미 클래스 추가
lib 디렉터리에 이름이 ad_helper.dart인 새 파일을 엽니다. 그런 다음 Android 및 iOS의 AdMob 앱 ID와 광고 단위 ID를 제공하는 AdHelper 클래스를 구현합니다.
AdMob 앱 ID(ca-app-pub-xxxxxx~yyyyy)와 광고 단위 ID(ca-app-pub-xxxxxxx/yyyyyyyy)를 이전 단계에서 만든 ID로 교체해야 합니다.
lib/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 UnsupportedError('Unsupported platform');
    }
  }
  static String get interstitialAdUnitId {
    if (Platform.isAndroid) {
      return '<YOUR_ANDROID_INTERSTITIAL_AD_UNIT_ID>';
    } else if (Platform.isIOS) {
      return '<YOUR_IOS_INTERSTITIAL_AD_UNIT_ID>';
    } else {
      throw UnsupportedError('Unsupported platform');
    }
  }
  static String get rewardedAdUnitId {
    if (Platform.isAndroid) {
      return '<YOUR_ANDROID_REWARDED_AD_UNIT_ID>';
    } else if (Platform.isIOS) {
      return '<YOUR_IOS_REWARDED_AD_UNIT_ID>';
    } else {
      throw UnsupportedError('Unsupported platform');
    }
  }
}
테스트 AdMob 앱 ID와 테스트 광고 단위 ID를 사용하려면 다음 코드 스니펫을 사용하세요.
lib/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';
    } else {
      throw new UnsupportedError('Unsupported platform');
    }
  }
  static String get interstitialAdUnitId {
    if (Platform.isAndroid) {
      return "ca-app-pub-3940256099942544/1033173712";
    } else if (Platform.isIOS) {
      return "ca-app-pub-3940256099942544/4411468910";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }
  static String get rewardedAdUnitId {
    if (Platform.isAndroid) {
      return "ca-app-pub-3940256099942544/5224354917";
    } else if (Platform.isIOS) {
      return "ca-app-pub-3940256099942544/1712485313";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }
}
6. Google 모바일 광고 SDK 초기화
광고를 로드하기 전에 Google 모바일 광고 SDK를 초기화해야 합니다. lib/home_route.dart 파일을 열고 홈페이지가 로드되기 전에 SDK가 초기화되도록 _initGoogleMobileAds()를 수정합니다.
초기화가 완료된 후 SDK 초기화 결과를 얻으려면 _initGoogleMobileAds() 메서드의 반환 유형을 Future<dynamic>에서 Future<InitializationStatus>로 변경해야 합니다.
home_route.dart
// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter/material.dart';
...
class HomeRoute extends StatelessWidget {
  ...
  Future<InitializationStatus> _initGoogleMobileAds() {
    // TODO: Initialize Google Mobile Ads SDK
    return MobileAds.instance.initialize();
  }
}
7. 배너 광고 추가
이 섹션에서는 다음 스크린샷에 표시된 대로 게임 화면의 상단에 배너 광고를 표시합니다.

- lib/game_route.dart파일을 열고- ad_manager.dart를 가져옵니다.
- 다음 행을 추가하여 ad_helper.dart및google_mobile_ads.dart를 가져옵니다.
lib/game_route.dart
...
// TODO: Import ad_helper.dart
import 'package:awesome_drawing_quiz/ad_helper.dart';
// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';
class GameRoute extends StatefulWidget {
  ...
}
- _GameRouteState클래스에서 배너 광고를 위해 다음 구성원을 추가합니다.
lib/game_route.dart
class _GameRouteState extends State<GameRoute> implements QuizEventListener {
  ...
  // TODO: Add _bannerAd
  BannerAd? _bannerAd;
  ...
}
- initState()메서드에서 320x50 배너(- AdSize.banner)를 위한- BannerAd를 만들어 로드합니다. 광고과 로드될 때 UI(- setState())를 업데이트하기 위해 광고 이벤트 리스너가 구성됩니다.
lib/game_route.dart
@override
void initState() {
  ...
  // TODO: Load a banner ad
  BannerAd(
    adUnitId: AdHelper.bannerAdUnitId,
    request: AdRequest(),
    size: AdSize.banner,
    listener: BannerAdListener(
      onAdLoaded: (ad) {
        setState(() {
          _bannerAd = ad as BannerAd;
        });
      },
      onAdFailedToLoad: (ad, err) {
        print('Failed to load a banner ad: ${err.message}');
        ad.dispose();
      },
    ),
  ).load();
}
- 사용 가능한 경우 배너 광고를 표시하도록 build()메서드를 수정합니다.
lib/game_route.dart
@override
Widget build(BuildContext context) {
  return Scaffold(
    ...
    body: SafeArea(
      child: Stack(
        children: [
          Center(
            ...
          ),
          // TODO: Display a banner when ready
          if (_bannerAd != null)
            Align(
              alignment: Alignment.topCenter,
              child: Container(
                width: _bannerAd!.size.width.toDouble(),
                height: _bannerAd!.size.height.toDouble(),
                child: AdWidget(ad: _bannerAd!),
              ),
            ),
        ],
      ),
    ),
    ...
  );
}
- dispose()콜백 메서드에서- BannerAd.dispose()메서드를 호출하여- BannerAd객체와 연결된 리소스를 릴리스합니다.
lib/game_route.dart
@override
void dispose() {
  // TODO: Dispose a BannerAd object
  _bannerAd?.dispose();
  ...
  super.dispose();
}
준비가 끝났습니다. 프로젝트를 실행하고 새 게임을 시작합니다. 광고가 로드된 후 화면의 상단에 배너 광고가 표시됩니다.

8. 전면 광고 추가
이 섹션에서는 게임(총 5단계)이 완료된 후 전면 광고를 표시합니다.
- lib/game_route.dart파일을 엽니다.
- _GameRouteState클래스에서 전면 광고를 위해 다음 구성원과 메서드를 추가합니다.
광고가 준비되었는지 확인하고(onAdLoaded() 및 onAdFailedToLoad()) 광고가 닫힌 경우(onAdDismissedFullScreenContent()) 앱의 홈 화면을 표시하기 위해 광고 이벤트 리스너가 구성됩니다
lib/game_route.dart
class _GameRouteState extends State<GameRoute> implements QuizEventListener {
  ...
  // TODO: Add _interstitialAd
 InterstitialAd? _interstitialAd;
  // TODO: Implement _loadInterstitialAd()
  void _loadInterstitialAd() {
    InterstitialAd.load(
      adUnitId: AdHelper.interstitialAdUnitId,
      request: AdRequest(),
      adLoadCallback: InterstitialAdLoadCallback(
        onAdLoaded: (ad) {
          ad.fullScreenContentCallback = FullScreenContentCallback(
            onAdDismissedFullScreenContent: (ad) {
              _moveToHome();
            },
          );
          setState(() {
            _interstitialAd = ad;
          });
        },
        onAdFailedToLoad: (err) {
          print('Failed to load an interstitial ad: ${err.message}');
        },
      ),
    );
  }
  ...
}
- 이 Codelab에서는 사용자가 5단계를 완료한 후 전면 광고가 표시됩니다. 불필요한 광고 요청을 최소화하려면 사용자가 3단계에 도달할 때 광고를 요청하세요.
onNewLevel() 메서드에 다음 행을 추가합니다.
lib/game_route.dart
@override
void onNewLevel(int level, Drawing drawing, String clue) {
  ...
  // TODO: Load an Interstitial Ad
  if (level >= 3 && _interstitialAd == null) {
    _loadInterstitialAd();
  }
}
- 게임이 완료되면 게임 점수 대화상자가 표시됩니다. 사용자가 대화상자를 닫으면 사용자를 Awesome Drawing Quiz의 홈 화면으로 보냅니다.
전면 광고는 화면 전환 간에 표시되어야 하므로 사용자가 닫기 버튼을 클릭하면 전면 광고가 표시됩니다.
다음과 같이 onGameOver() 메서드를 수정합니다.
lib/game_route.dart
@override
void onGameOver(int correctAnswers) {
  showDialog(
    context: _scaffoldKey.currentContext,
    builder: (context) {
      return AlertDialog(
        title: Text('Game over!'),
        content: Text('Score: $correctAnswers/5'),
        actions: [
          FlatButton(
            child: Text('close'.toUpperCase()),
            onPressed: () {
              // TODO: Display an Interstitial Ad
              if (_interstitialAd != null) {
                _interstitialAd?.show();
              } else {
                _moveToHome();
              }
            },
          ),
        ],
      );
    },
  );
}
- dispose()콜백 메서드에서- InterstitialAd.dispose()메서드를 호출하여- InterstitialAd객체와 연결된 리소스를 릴리스합니다.
lib/game_route.dart
@override
void dispose() {
  ...
  // TODO: Dispose an InterstitialAd object
  _interstitialAd?.dispose();
  ...
  super.dispose();
}
준비가 끝났습니다. 프로젝트를 실행하고 게임을 완료합니다. 전면 광고가 로드된 경우 점수 대화상자에서 닫기 버튼을 클릭하면 전면 광고가 표시됩니다.

9. 보상형 광고 추가
이 섹션에서는 사용자에게 추가 힌트를 리워드로 제공하는 보상형 광고를 추가합니다.
- lib/game_route.dart파일을 엽니다.
- _GameRouteState클래스에서 보상형 광고를 위해 구성원을 추가하고- _loadRewardedAd()메서드를 구현합니다. 이 메서드는 광고를 최대한 빨리 캐시하기 위해 광고가 닫힐 때(- onAdDismissedFullScreenContent) 다른 보상형 광고를 로드합니다.
lib/game_route.dart
class _GameRouteState extends State<GameRoute> implements QuizEventListener {
  ...
  // TODO: Add _rewardedAd
  RewardedAd? _rewardedAd;
  // TODO: Implement _loadRewardedAd()
  void _loadRewardedAd() {
    RewardedAd.load(
      adUnitId: AdHelper.rewardedAdUnitId,
      request: AdRequest(),
      rewardedAdLoadCallback: RewardedAdLoadCallback(
        onAdLoaded: (ad) {
          ad.fullScreenContentCallback = FullScreenContentCallback(
            onAdDismissedFullScreenContent: (ad) {
              setState(() {
                ad.dispose();
                _rewardedAd = null;
              });
              _loadRewardedAd();
            },
          );
          setState(() {
            _rewardedAd = ad;
          });
        },
        onAdFailedToLoad: (err) {
          print('Failed to load a rewarded ad: ${err.message}');
        },
      ),
    );
  }
  ...
}
- 게임이 시작할 때 보상형 광고를 요청하려면 initState()메서드에서_loadRewardedAd()를 호출합니다.
lib/game_route.dart
class _GameRouteState extends State<GameRoute> implements QuizEventListener {
  ...
  @override
  void initState() {
    ...
    // COMPLETE: Load a Rewarded Ad
    _loadRewardedAd();
  }
  ...
}
- 사용자가 플로팅 작업 버튼을 클릭하여 보상형 광고를 볼 수 있도록 허용합니다. 버튼은 사용자가 현재 레벨에서 힌트를 사용하지 않았으며 보상형 광고가 로드된 경우에만 표시됩니다.
플로팅 작업 버튼을 표시하려면 _buildFloatingActionButton() 메서드를 다음과 같이 수정합니다. null을 반환하면 화면에서 버튼이 숨겨집니다.
onUserEarnedReward가 보상형 광고에서 가장 중요한 광고 이벤트입니다. 이 이벤트는 사용자가 리워드를 받을 수 있게 될 때(예를 들어 동영상 시청을 완료할 때) 트리거됩니다.
이 Codelab에서 QuizManager.instance.useHint() 메서드는 콜백에서 호출되어 힌트 문자열에서 1자를 더 표시합니다. 앱에서는 onAdClosed 콜백의 보상형 광고를 새로고침하여 최대한 빨리 광고가 준비될 수 있도록 합니다.
lib/game_route.dart
Widget? _buildFloatingActionButton() {
  // TODO: Return a FloatingActionButton if a rewarded ad is available
  return (!QuizManager.instance.isHintUsed && _rewardedAd != null)
      ? FloatingActionButton.extended(
          onPressed: () {
            showDialog(
              context: context,
              builder: (context) {
                return AlertDialog(
                  title: Text('Need a hint?'),
                  content: Text('Watch an Ad to get a hint!'),
                  actions: [
                    TextButton(
                      child: Text('cancel'.toUpperCase()),
                      onPressed: () {
                        Navigator.pop(context);
                      },
                    ),
                    TextButton(
                      child: Text('ok'.toUpperCase()),
                      onPressed: () {
                        Navigator.pop(context);
                        _rewardedAd?.show(
                          onUserEarnedReward: (_, reward) {
                            QuizManager.instance.useHint();
                          },
                        );
                      },
                    ),
                  ],
                );
              },
            );
          },
          label: Text('Hint'),
          icon: Icon(Icons.card_giftcard),
        )
      : null;
}
- dispose()콜백 메서드에서- RewardedAd.dispose()메서드를 호출하여- RewardedAd객체와 연결된 리소스를 릴리스합니다.
lib/game_route.dart
@override
void dispose() {
  ...
  // TODO: Dispose a RewardedAd object
  _rewardedAd?.dispose();
  ...
  super.dispose();
}
준비가 끝났습니다. 프로젝트를 실행하고 게임을 플레이합니다. 보상형 광고가 로드되면 화면 하단에 힌트 버튼이 표시됩니다. 추가 힌트를 얻으려면 힌트 버튼을 클릭하세요.

10. 모두 완료했습니다.
Codelab을 완료했습니다. 이 Codelab의 완료된 코드는  complete 폴더에서 확인할 수 있습니다.
complete 폴더에서 확인할 수 있습니다.
배너 광고와 네이티브 인라인 광고를 구현하는 방법을 알아보려면 Flutter 앱에 AdMob 배너 광고 및 네이티브 인라인 광고 추가 Codelab을 확인하세요.
자세한 내용은 다른 Flutter Codelab을 참고하세요.




