반응형
Overview
앱 내의 광고를 탑재하여 수익을 창출 할 수 있습니다.
이번 포스팅에서는 구글 애드몹 패키지를 활용하여 플러터에 광고를 탑재하고 표시하는 것을 다룹니다.
- 앱 시작시 전면 광고 출력
- 앱 상단의 배너 광고 표시
- 앱 바 우측 상단의 달러 모양 '$' 클릭 시 보상형 광고 표시 및 보상 지급 안내 문구 출력
포스팅에서 진행한 환경
- OS : Windows 10
- IDE : Visual Studio Code
- Emulator : Pixel 3a API 33 (Android-x86 emulator)
포스팅에서의 예시 프로젝트 다운로드
- 포스팅에서 다루는 예시 프로젝트는 아래의 깃허브 링크에서 다운로드 받을 수 있습니다.
- '8_add_google_mobile_ads' 폴더를 확인해주세요.
- https://github.com/luvris2/flutter_memo_app
다른 설명이 필요하면 아래의 링크를 참고해주세요.
- 01. Flutter - ListView, Card, Navigator - 스크롤 가능한 목록 표시, 목록 선택시 특정 목록 내용 보여주기
- 02. Flutter - StatefulWidget 활용 - 실시간 검색 기능 구현하기
- 03. Flutter - Floating Action Button, showDialog - 플로팅 액션 버튼으로 특정 작업 수행하기(리스트뷰 항목 추가하기)
- 04. Flutter - BottomNavigationBar - 다른 페이지로 이동하기(화면 전환)
- 05. Flutter - 파일 분리하기, class 나누기, 위젯 리소스화하기
- 06. Flutter - 로그인, 로그아웃 구현하기 (1/3) - MySQL DB 연동, 레이아웃 설계
- 06. Flutter - 로그인, 로그아웃 구현하기 (2/3) - 로그인, 로그아웃 기능 구현
- 06. Flutter - 로그인, 로그아웃 구현하기 (3/3) - 자동 로그인 기능 구현
- 07. Flutter - 메모 추가/수정/삭제하기 (1/2) - MySQL 연동, Provider 설정, 메모 조회
- 07. Flutter - 메모 추가/수정/삭제하기 (2/2) - 메모 앱 만들기 기능 구현
구글 애드몹 (Google AdMob)
- 인앱 광고를 비롯하여 앱 비지니스 성장을 위한 강력하고 손쉬운 제품
- 앱 개발자가 광고를 표시하여 모바일 앱에서 수익을 창출할 수 있는 모바일 광고 플랫폼
- 다양한 유형의 광고를 통해 수익 제공
- 배너 광고 : 기기 화면의 상단이나 하단에 표시되는 직사각형 광고입니다. 배너 광고는 사용자가 앱과 상호작용하는 동안 화면에 표시되며 일정 시간이 지나면 자동으로 새로고침될 수 있습니다. 모바일 광고를 처음 시작하는 경우 이 형식부터 이용해 보시기 바랍니다.
- 전면 광고 : 사용자가 닫을 때까지 앱의 인터페이스를 완전히 덮는 전체 화면 광고입니다. 게임 레벨이 바뀌는 사이 또는 작업 완료 직후와 같이 앱 이용이 잠시 중단될 때 자연스럽게 광고가 게재되는 것이 가장 좋습니다.
- 보상형 광고 : 짧은 동영상을 시청하거나 플레이어블 광고 및 설문조사와 상호작용한 사용자에게 보상을 제공하는 광고 무료 게임 사용자로부터 수익을 창출하는 데 효과적입니다.
- 네이티브 광고 : 앱의 디자인과 분위기에 어울리게 맞춤설정할 수 있는 광고입니다. 광고 배치 방법 및 위치를 정할 수 있으므로 광고 레이아웃과 앱 디자인의 일관성 유지가 가능합니다.
구글 애드몹 시작하기 / 앱 추가하기
- 애드몹 홈페이지 접속
- https://admob.google.com/intl/ko/home/
- '시작하기' 버튼 클릭
- 본인 인증 및 로그인
- 이후 초기 시작 관련 페이지를 넘어가고 나면 아래와 같은 홈페이지로 이동
- 저 같은 경우는 이미 블로그 애드센스가 있기 때문에 따로 화면 첨부를 하지 못하였습니다.
- 구글 애드몹 페이지의 홈 메뉴에서 '시작하기' 버튼 클릭
- 플랫폼 선택 : Android / iOS
- 광고를 탑재할 앱이 지원하는 플랫폼 선택
- 앱 스토어 등록 여부 선택
- 앱이 스토어에 등록되어 있을 경우 예, 아닐 경우 아니오
- '계속' 버튼 클릭
- 앱 이름 지정
- 구분 할 이름 설정
- 사용자 측정항목 선택
- 설정 : Firebse에 연결하여 구글 애널리틱스 계정 데이터를 사용할 수 있음, 제품 기능이 향상되고 앱 수익이 늘어 날 수 있음
- 설정 해제 : 구글 애널리틱스 데이터 사용하지 않음
- '앱 추가' 버튼 클릭
- '완료' 버튼 클릭
- '앱' 메뉴에서 추가한 광고 앱이 생성되었는지 확인
- 카테고리의 '앱 설정' 클릭 - 앱 ID 확인
플러터에서의 애드몹 설정
구글 애드 몹 기본 요건
- Flutter 1.22.0 이상
- Android
- Android 스튜디오 3.2 이상
- Android API 수준 20 이상 타겟팅
- compileSdkVersion을 28 이상으로 설정
- iOS
- 사용 설정된 명령줄 도구가 포함된 최신 버전의 Xcode
- 권장사항: AdMob 계정을 만들고 Android 또는 iOS 앱을 등록하세요.
프로젝트에 애드몹 패키지 추가
- 터미널에서 아래의 코드 입력
flutter pub add google_mobile_ads
Android/iOS 기기별 설정
기기에 따라 아래의 설정을 진행해주세요.
안드로이드 Manifest.xml 설정
- (프로젝트 디렉토리 내) android - app - src - main - AndroidManifest.xml 파일 열기
- <application> 하위에 <meta-data> 코드 추가
- <meta-data> 태그를 복사하여 Manifest.xml에 추가
- 구글 애드몹 페이지에서 광고 앱을 추가한 앱 ID 입력
<manifest>
<application>
<!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
<application>
<manifest>
iOS Info.plist 설정
- (프로젝프 디렉토리 내) ios - Runner - Info.plist 파일 열기
- 키 추가
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-################~##########</string>
코딩 시 애드몹 참고사항
테스트 광고 (데모 광고) ID
* 통합 앱 ID가 생겨서 실제로 발급 받을 때는 디바이스에 따라 ID 부여를 하지 않아도 됩니다.
- Android
광고 형식 | 데모 광고 단위 ID |
App Open | ca-app-pub-3940256099942544/3419835294 |
Banner | ca-app-pub-3940256099942544/6300978111 |
Interstitial | ca-app-pub-3940256099942544/1033173712 |
Interstitial Video | ca-app-pub-3940256099942544/8691691433 |
Rewarded | ca-app-pub-3940256099942544/5224354917 |
Rewarded Interstitial | ca-app-pub-3940256099942544/5354046379 |
Native Advanced | ca-app-pub-3940256099942544/2247696110 |
Native Advanced Video | ca-app-pub-3940256099942544/1044960115 |
- iOS
광고 형식 | 데모 광고 단위 ID |
앱 오프닝 광고 | ca-app-pub-3940256099942544/5662855259 |
배너 | ca-app-pub-3940256099942544/2934735716 |
전면 광고 | ca-app-pub-3940256099942544/4411468910 |
동영상 전면 광고 | ca-app-pub-3940256099942544/5135589807 |
보상형 광고 | ca-app-pub-3940256099942544/1712485313 |
보상형 전면 광고 | ca-app-pub-3940256099942544/6978759866 |
네이티브 광고 고급형 | ca-app-pub-3940256099942544/3986624511 |
네이티브 광고 고급형 동영상 | ca-app-pub-3940256099942544/2521693316 |
배너 광고 크기
크기(폭x높이) | 설명 | AdSize 상수 |
320x50 | 표준 배너 | banner |
320x100 | 대형 배너 | largeBanner |
320x250 | 중간 직사각형 | mediumRectangle |
468x60 | 전체 크기 배너 | fullBanner |
728x90 | 리더보드 | leaderboard |
화면 폭x32|50|90 | 스마트 배너 | getSmartBanner(Orientation) 사용 |
광고 ID 만들기
- (구글 애드몹 홈페이지의 메뉴에서) 앱 - 자신의 앱 클릭
- 선택한 앱의 '광고 단위' 카테고리 클릭
- '광고 단위 추가' 버튼 클릭
- 지급 받을 광고 ID 종류 선택
- 광고 단위 이름과 설정을 지정
- '광고 단위 만들기' 버튼 클릭
- 이 후 목록에서 광고 단위를 누르면 자신의 광고 ID를 확인할 수 있음
코딩
모바일 광고 인스턴스 초기화
- 광고를 로드하기 전에 앱에서 모바일 광고 SDK를 초기화해야합니다.
- 초기화가 되거나 제한 시간인 30초 후에 Future 객체가 반환됩니다.
- 초기화 작업은 앱을 실행하기 직전에 한 번만 하는 것이 좋습니다.
- google mobile ads 패키지를 임포트하고 메인 함수에 아래의 코드 추가합니다.
- 패키지 임포트는 코드를 추가한 후, 퀵 픽스를 통해 쉽게 할 수 있습니다.
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:flutter_memo_app/loginPage/loginMainPage.dart';
import 'package:flutter_memo_app/memoPage/memoListProvider.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:provider/provider.dart';
void main() {
// 플러터 바인딩 초기화 및 바인딩 설정 체크
WidgetsFlutterBinding.ensureInitialized();
// 모바일 광고 SDK 초기화
MobileAds.instance.initialize();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => MemoUpdator()),
],
child: const MyApp(),
),
);
}
애드몹 광고 클래스 정의
1) 광고를 보여주는 로직을 메인 코드 내에 작성하여 할 경우, 코드가 매우 복잡해지기 때문에 클래스를 생성하여 호출하는 형식으로 진행합니다.
2) 이 포스팅에서는 static 키워드를 사용하여 싱글톤으로 구현하였으나 여러가지 광고 보상의 다양성을 구현하려면 static키워드를 빼고 코드를 구현하시길 바랍니다.
애드몹 광고 클래스를 정의할 파일을 생성합니다.
- 파일명 : googleAdMob.dart
- 코드 내에 필요한 파일들을 호출(import)합니다.
import 'package:flutter/material.dart';
// 비동기 작업의 완료 상태 확인을 위함
import 'dart:async';
// 구글 애드몹 사용을 위함
import 'package:google_mobile_ads/google_mobile_ads.dart';
- 클래스를 정의 합니다.
- 포스팅에서는 GoogleAdMob 이라고 정의하였습니다.
class GoogleAdMob {
// 이후 광고 코드는 다 여기에 입력
}
배너 광고
배너 광고 인스턴스 생성
- 배너 광고는 앱 실행시 딱 한번만 로드해도 되기 때문에 인스턴스 생성 함수를 정의합니다.
- 이 함수는 플러터 앱이 실행 될 때 호출합니다.
// 배너 광고 인스턴스를 생성하고 로딩하기 위한 함수
static BannerAd loadBannerAd() {
// 배너 광고
BannerAd myBanner = BannerAd(
adUnitId: 'ca-app-pub-9914634594152112/4988082034', // 내 리워드 광고 통합 APP ID
/*
크기(폭x높이) 설명 AdSize 상수
-----------------------------------------------------------------------
320x50 표준 배너 banner
320x100 대형 배너 largeBanner
320x250 중간 직사각형 mediumRectangle
468x60 전체 크기 배너 fullBanner
728x90 리더보드 leaderboard
화면 폭x32|50|90 스마트 배너 getSmartBanner(Orientation) 사용
*/
size: AdSize.banner,
request: AdRequest(),
listener: BannerAdListener(
// 광고가 성공적으로 수신된 경우
onAdLoaded: (Ad ad) => print('Ad loaded.'),
// 광고 요청이 실패한 경우
onAdFailedToLoad: (Ad ad, LoadAdError error) {
// 광고 요청 오류 시 광고를 삭제하여 리소스 확보
ad.dispose();
print('Ad failed to load: $error');
},
// 광고가 화면을 덮는 오버레이를 열었을 때 호출
// 사용자가 광고를 클릭하거나 특정 조건이 충족되어 광고가 표시 될 때 발생
onAdOpened: (Ad ad) => print('Ad opened.'),
// 광고가 화면을 덮는 오버레이를 닫았을 때 호출
// 사용자가 광고를 닫거나 자동으로 닫힐 때 발생
onAdClosed: (Ad ad) => print('Ad closed.'),
// 광고가 노출 될 때 호출
// 광고가 사용자에게 보여질 때 발생
onAdImpression: (Ad ad) => print('Ad impression.'),
),
);
return myBanner;
}
배너 광고 노출하기
- 플러터 앱이 실행될 때 호출된 배너 광고의 인스턴스를 매개 변수로 입력합니다.
- 배너 광고 노출을 위해 UI 디자인을 합니다. 반환 값은 컨테이너 위젯으로 호출 시 바로 노출되게끔 설계합니다.
- 물론, 환경에 따라 직접 UI 디자인을 하셔도 됩니다.
// 배너 광고를 화면에 보여주기 위한 함수, 파라미터로 로드된 배너 인스턴스 필요
static Container showBannerAd(BannerAd myBanner) {
// 광고 디스플레이
// 배너 광고를 위젯으로 표시하기 위해 지원되는 광고를 사용하여 AdWidget 인스턴스화
final Container adContainer = Container(
alignment: Alignment.center,
width: myBanner.size.width.toDouble(),
height: myBanner.size.height.toDouble(),
child: AdWidget(ad: myBanner),
);
return adContainer;
}
전면 광고 노출하기
- 전면 광고는 1회성이기 때문에 호출 시 인스턴스를 생성하고 바로 광고를 보여주도록 합니다.
- 반환 값은 없으며 호출 즉시 광고가 노출됩니다.
// 전면 광고를 인스턴스를 생성하고 로딩하기 위한 함수
static void showInterstitialAd() {
// 전면 광고 로드
InterstitialAd.load(
adUnitId: 'ca-app-pub-9914634594152112/3639341340', // 내 리워드 광고 통합 APP ID
request: AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
// 전면 광고 로드 완료
onAdLoaded: (InterstitialAd ad) {
// 광고 보여주기
ad.show();
// 리소스 해제
ad.dispose();
},
// 전면 광고 로드 실패
onAdFailedToLoad: (LoadAdError error) {
print('InterstitialAd failed to load: $error');
},
),
);
}
보상형 광고 노출하기
블로그를 작성하면서 가장 많이 애를 먹은 부분입니다... 리워드 관련으로 반환값 때문에...
- 보상 지급하는 방법
- 구글 애드몹 홈페이지에서 해당 보상형 광고 단위 설정 페이지를 들어갑니다.
- 리워드 설정 부분의 '리워드 수량' 과 '리워드 상품'을 입력합니다.
- 플러터 코드 설계 시, 지급되는 reward 변수의 속성명은 amount(리워드 수량), type(리워드 상품)으로 구분됩니다.
- 보상 확인 방법
- Completer 클래스를 이용하여 비동기 작업의 상태를 확인합니다.
- 비동기 작업이 끝나면 작업의 값을 반환합니다.
- 반환된 값을 토대로 보상을 지급합니다.
- reward의 amount, type은 애드몹 홈페이지에서 설정한 값을 반환합니다.
- 리워드 수량 : 1 / 리워드 상품 : Reward
// 보상형 광고 보여주기 위한 함수, bool 반환값을 통해 보상이 지급되었는지 확인
static Future<bool> showRewardedAd() async {
print('******************showRewardedAD******************');
// 비동기 작업을 확인하기 위한 bool 타입의 Completer
Completer<bool> completer = Completer<bool>();
// 보상 지급 확인 변수
bool isRewarded = false;
await RewardedAd.load(
adUnitId: 'ca-app-pub-9914634594152112/1617126316', // 내 리워드 광고 통합 APP ID
request: AdRequest(),
rewardedAdLoadCallback: RewardedAdLoadCallback(
// 보상 광고 로드 완료
onAdLoaded: (rewardedAd) async {
rewardedAd.fullScreenContentCallback = FullScreenContentCallback(
// 광고가 표시 될 때 호출
onAdShowedFullScreenContent: (ad) =>
print('$ad onAdShowedFullScreenContent.'),
// 광고가 닫힐 때 호출
onAdDismissedFullScreenContent: (ad) {
print('$ad onAdDismissedFullScreenContent.');
// 광고 리소스 해제
ad.dispose();
completer.complete(isRewarded);
},
// 광고가 표시되지 못했을 때의 호출, 오류 정보 제공
onAdFailedToShowFullScreenContent: (ad, AdError error) {
print('$ad onAdFailedToShowFullScreenContent: $error');
ad.dispose();
completer.complete(isRewarded);
},
// 광고가 노출되었을 때 호출
onAdImpression: (ad) => print('$ad impression occurred.'),
);
// 광고 보여주기
await rewardedAd.show(
onUserEarnedReward: (ad, reward) {
// 광고 보상 지급 코드
print(
'Reward ad : $ad reward : $reward, type : ${reward.type}, amount : ${reward.amount}');
// 보상 지급 완료 처리
isRewarded = true;
},
);
},
// 보상 광고 로드 실패
onAdFailedToLoad: (LoadAdError error) {
print('RewardedAd failed to load: $error');
completer.complete(isRewarded);
},
),
);
return completer.future;
}
메인 페이지에 광고 기능 붙이기
기능 설계
- 배너 광고
- 메모 페이지 호출 시 배너 광고 표시
- 배너 광고의 위치는 앱의 가장 상단에 표시
- 단, 앱 바 영역을 침범해서는 안된다. (앱 바 바로 밑에 위치하도록 설계)
- 전면 광고
- 메모 페이지 호출 시 전면 광고 표시
- 전면 광고는 앱 실행 시 단 한 번만 노출되도록 한다.
- 보상형 광고
- 앱의 앱 바 우측 상단에 표시
- 아이콘은 달러 모양으로하여 직관적으로 보상형 광고임을 암시하도록 한다.
- 아이콘 클릭 시 보상형 광고가 노출되며, 광고를 모두 볼 경우 보상을 지급하는 로직을 작성한다.
코드
- 앱 실행 시 배너 광고, 전면 광고 노출하기
- 페이지가 표시 될 때 initState에 코드를 작성하여 광고를 바로 보여줄 수 있도록 함
/* memoMainPage.dart */
// 애드몹 배너 객체 생성
late BannerAd myBanner;
@override
void initState() {
// TODO: implement initState
super.initState();
// 메모 리스트 출력
getMemoList();
// 배너 광고 로드
myBanner = GoogleAdMob.loadBannerAd();
myBanner.load();
// 전면 광고 로드
GoogleAdMob.showInterstitialAd();
}
- 앱 바의 보상형 광고 버튼
- 보상형 광고 버튼을 추가하고 광고의 보상을 지급함
- 광고 보상에 대한 로직은 지급하는 상품, 갯수, 방식이 다르므로 각자 구현하세요!
/* header&footer.dart */
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CupertinoNavigationBar(
middle: const Text('Title'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// 보상형 광고 보기 버튼
CupertinoButton(
padding: EdgeInsets.zero,
child: Icon(
CupertinoIcons.money_dollar_circle,
size: 30,
),
onPressed: () async {
// 보상형 광고 보여주기
var rewardedAds = await GoogleAdMob.showRewardedAd();
// 광고 보상 지급 완료 메시지
if (rewardedAds) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Text('보상이 지급되었습니다.'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // 다이얼로그 닫기,
},
child: Text('닫기'),
),
],
);
},
);
}
},
),
// 코드 생략
전체 소스 코드
- googleAdMob.dart
- 광고 정의 클래스
더보기
import 'package:flutter/material.dart';
// 비동기 작업의 완료 상태 확인을 위함
import 'dart:async';
// 구글 애드몹 사용을 위함
import 'package:google_mobile_ads/google_mobile_ads.dart';
class GoogleAdMob {
// 배너 광고 인스턴스를 생성하고 로딩하기 위한 함수
static BannerAd loadBannerAd() {
// 배너 광고
BannerAd myBanner = BannerAd(
adUnitId: 'ca-app-pub-9914634594152112/4988082034', // 내 리워드 광고 통합 APP ID
/*
크기(폭x높이) 설명 AdSize 상수
-----------------------------------------------------------------------
320x50 표준 배너 banner
320x100 대형 배너 largeBanner
320x250 중간 직사각형 mediumRectangle
468x60 전체 크기 배너 fullBanner
728x90 리더보드 leaderboard
화면 폭x32|50|90 스마트 배너 getSmartBanner(Orientation) 사용
*/
size: AdSize.banner,
request: AdRequest(),
listener: BannerAdListener(
// 광고가 성공적으로 수신된 경우
onAdLoaded: (Ad ad) => print('Ad loaded.'),
// 광고 요청이 실패한 경우
onAdFailedToLoad: (Ad ad, LoadAdError error) {
// 광고 요청 오류 시 광고를 삭제하여 리소스 확보
ad.dispose();
print('Ad failed to load: $error');
},
// 광고가 화면을 덮는 오버레이를 열었을 때 호출
// 사용자가 광고를 클릭하거나 특정 조건이 충족되어 광고가 표시 될 때 발생
onAdOpened: (Ad ad) => print('Ad opened.'),
// 광고가 화면을 덮는 오버레이를 닫았을 때 호출
// 사용자가 광고를 닫거나 자동으로 닫힐 때 발생
onAdClosed: (Ad ad) => print('Ad closed.'),
// 광고가 노출 될 때 호출
// 광고가 사용자에게 보여질 때 발생
onAdImpression: (Ad ad) => print('Ad impression.'),
),
);
return myBanner;
}
// 배너 광고를 화면에 보여주기 위한 함수, 파라미터로 로드된 배너 인스턴스 필요
static Container showBannerAd(BannerAd myBanner) {
// 광고 디스플레이
// 배너 광고를 위젯으로 표시하기 위해 지원되는 광고를 사용하여 AdWidget 인스턴스화
final Container adContainer = Container(
alignment: Alignment.center,
width: myBanner.size.width.toDouble(),
height: myBanner.size.height.toDouble(),
child: AdWidget(ad: myBanner),
);
return adContainer;
}
// 전면 광고를 인스턴스를 생성하고 로딩하기 위한 함수
static void showInterstitialAd() {
// 전면 광고 로드
InterstitialAd.load(
adUnitId: 'ca-app-pub-9914634594152112/3639341340', // 내 리워드 광고 통합 APP ID
request: AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
// 전면 광고 로드 완료
onAdLoaded: (InterstitialAd ad) {
// 광고 보여주기
ad.show();
// 리소스 해제
ad.dispose();
},
// 전면 광고 로드 실패
onAdFailedToLoad: (LoadAdError error) {
print('InterstitialAd failed to load: $error');
},
),
);
}
// 보상형 광고 보여주기 위한 함수, bool 반환값을 통해 보상이 지급되었는지 확인
static Future<bool> showRewardedAd() async {
print('******************showRewardedAD******************');
// 비동기 작업을 확인하기 위한 bool 타입의 Completer
Completer<bool> completer = Completer<bool>();
// 보상 지급 확인 변수
bool isRewarded = false;
await RewardedAd.load(
adUnitId: 'ca-app-pub-9914634594152112/1617126316', // 내 리워드 광고 통합 APP ID
request: AdRequest(),
rewardedAdLoadCallback: RewardedAdLoadCallback(
// 보상 광고 로드 완료
onAdLoaded: (rewardedAd) async {
rewardedAd.fullScreenContentCallback = FullScreenContentCallback(
// 광고가 표시 될 때 호출
onAdShowedFullScreenContent: (ad) =>
print('$ad onAdShowedFullScreenContent.'),
// 광고가 닫힐 때 호출
onAdDismissedFullScreenContent: (ad) {
print('$ad onAdDismissedFullScreenContent.');
// 광고 리소스 해제
ad.dispose();
completer.complete(isRewarded);
},
// 광고가 표시되지 못했을 때의 호출, 오류 정보 제공
onAdFailedToShowFullScreenContent: (ad, AdError error) {
print('$ad onAdFailedToShowFullScreenContent: $error');
ad.dispose();
completer.complete(isRewarded);
},
// 광고가 노출되었을 때 호출
onAdImpression: (ad) => print('$ad impression occurred.'),
);
// 광고 보여주기
await rewardedAd.show(
onUserEarnedReward: (ad, reward) {
// 광고 보상 지급 코드
print(
'Reward ad : $ad reward : $reward, type : ${reward.type}, amount : ${reward.amount}');
// 보상 지급 완료 처리
isRewarded = true;
},
);
},
// 보상 광고 로드 실패
onAdFailedToLoad: (LoadAdError error) {
print('RewardedAd failed to load: $error');
completer.complete(isRewarded);
},
),
);
return completer.future;
}
}
- memoMainPage.dart
- 배너 광고, 전면 광고 표시 페이지
더보기
// 메모 페이지
// 앱의 상태를 변경해야하므로 StatefulWidget 상속
// ignore_for_file: avoid_print, use_build_context_synchronously
import 'package:flutter/material.dart';
import 'package:flutter_memo_app/adMob/googleAdMob.dart';
import 'package:flutter_memo_app/memoPage/memoDB.dart';
import 'package:flutter_memo_app/memoPage/memoListProvider.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:provider/provider.dart';
import 'memoDetailPage.dart';
class MyMemoPage extends StatefulWidget {
const MyMemoPage({super.key});
@override
MyMemoState createState() => MyMemoState();
}
class MyMemoState extends State<MyMemoPage> {
// 검색어
String searchText = '';
// 플로팅 액션 버튼을 이용하여 항목을 추가할 제목과 내용
final TextEditingController titleController = TextEditingController();
final TextEditingController contentController = TextEditingController();
// 메모 리스트 저장 변수
List items = [];
// 메모 리스트 출력
Future<void> getMemoList() async {
List memoList = [];
// DB에서 메모 정보 호출
var result = await selectMemoALL();
print(result?.numOfRows);
// 메모 리스트 저장
for (final row in result!.rows) {
var memoInfo = {
'id': row.colByName('id'),
'userIndex': row.colByName('userIndex'),
'userName': row.colByName('userName'),
'memoTitle': row.colByName('memoTitle'),
'memoContent': row.colByName('memoContent'),
'createDate': row.colByName('createDate'),
'updateDate': row.colByName('updateDate')
};
memoList.add(memoInfo);
}
print('MemoMainPage - getMemoList : $memoList');
context.read<MemoUpdator>().updateList(memoList);
}
// 애드몹 배너 객체 생성
late BannerAd myBanner;
@override
void initState() {
// TODO: implement initState
super.initState();
// 메모 리스트 출력
getMemoList();
// 배너 광고 로드
myBanner = GoogleAdMob.loadBannerAd();
myBanner.load();
// 전면 광고 로드
GoogleAdMob.showInterstitialAd();
// 보상 광고 로드
//GoogleAdMob.showRewardedAd();
}
// 리스트뷰 카드 클릭 이벤트
void cardClickEvent(BuildContext context, int index) async {
dynamic content = items[index];
print('content : $content');
// 메모 리스트 업데이트 확인 변수 (false : 업데이트 되지 않음, true : 업데이트 됨)
var isMemoUpdate = await Navigator.push(
context,
MaterialPageRoute(
// 정의한 ContentPage의 폼 호출
builder: (context) => ContentPage(content: content),
),
);
// 메모 수정이 일어날 경우, 메모 메인 페이지의 리스트 새로고침
if (isMemoUpdate != null) {
setState(() {
getMemoList();
items = Provider.of<MemoUpdator>(context, listen: false).memoList;
});
}
}
// 플로팅 액션 버튼 클릭 이벤트
Future<void> addItemEvent(BuildContext context) {
// 다이얼로그 폼 열기
return showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('메모 추가'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
controller: titleController,
decoration: InputDecoration(
labelText: '제목',
),
),
TextField(
controller: contentController,
maxLines: null, // 다중 라인 허용
decoration: InputDecoration(
labelText: '내용',
),
),
],
),
actions: <Widget>[
TextButton(
child: Text('취소'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('추가'),
onPressed: () async {
String title = titleController.text;
String content = contentController.text;
// 메모 추가
await addMemo(title, content);
setState(() {
// 메모 리스트 새로고침
print("MemoMainPage - addMemo/setState");
getMemoList();
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
// 상단에 배너 광고 표시
GoogleAdMob.showBannerAd(myBanner),
Padding(
padding: const EdgeInsets.all(20.0),
child: TextField(
decoration: InputDecoration(
hintText: '검색어를 입력해주세요.',
border: OutlineInputBorder(),
),
onChanged: (value) {
setState(() {
searchText = value;
});
},
),
),
Expanded(
child: Builder(
builder: (context) {
// 메모 수정이 일어날 경우 메모 리스트 새로고침
items = context.watch<MemoUpdator>().memoList;
// 메모가 없을 경우의 페이지
if (items.isEmpty) {
return Center(
child: Text(
"표시할 메모가 없습니다.",
style: TextStyle(fontSize: 20),
),
);
}
// 메모가 있을 경우의 페이지
else {
// items 변수에 저장되어 있는 모든 값 출력
return ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
// 메모 정보 저장
dynamic memoInfo = items[index];
String userName = memoInfo['userName'];
String memoTitle = memoInfo['memoTitle'];
String memoContent = memoInfo['memoContent'];
String createDate = memoInfo['createDate'];
String updateDate = memoInfo['updateDate'];
// 검색 기능, 검색어가 있을 경우, 제목으로만 검색
if (searchText.isNotEmpty &&
!items[index]['memoTitle']
.toLowerCase()
.contains(searchText.toLowerCase())) {
return SizedBox.shrink();
}
// 검색어가 없을 경우
// 혹은 모든 항목 표시
else {
return Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.elliptical(20, 20))),
child: ListTile(
leading: Text(userName),
title: Text(memoTitle),
subtitle: Text(memoContent),
trailing: Text(updateDate),
onTap: () => cardClickEvent(context, index),
),
);
}
},
);
}
},
),
),
],
),
// 플로팅 액션 버튼
floatingActionButton: FloatingActionButton(
onPressed: () => addItemEvent(context), // 버튼을 누를 경우 메모 추가 UI 표시
tooltip: 'Add Item', // 플로팅 액션 버튼 설명
child: Icon(Icons.add), // + 모양 아이콘
),
);
}
}
- header&footer.dart
- 상 단의 앱 바에 보상 광고 보기 버튼 페이지
더보기
// 기본 홈
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_memo_app/adMob/googleAdMob.dart';
import 'package:flutter_memo_app/loginPage/loginMainPage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'communityPage/communityMainPage.dart';
import 'memoPage/memoMainPage.dart';
import 'myInfoPage/myInfoMainPage.dart';
class MyAppPage extends StatefulWidget {
const MyAppPage({super.key});
@override
State<MyAppPage> createState() => MyAppState();
}
class MyAppState extends State<MyAppPage> {
// 바텀 내비게이션 바 인덱스
int _selectedIndex = 0;
// 바텀 내비게이션 바의 연결될 페이지를 인덱스 순으로 나열
final List<Widget> _navIndex = [
MyMemoPage(),
CommunityPage(),
MyInfoPage(),
];
// 바텀 내비게이션 바가 클릭되었을 때 인덱스 상태 변경
void _onNavTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CupertinoNavigationBar(
middle: const Text('Title'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// 보상형 광고 보기 버튼
CupertinoButton(
padding: EdgeInsets.zero,
child: Icon(
CupertinoIcons.money_dollar_circle,
size: 30,
),
onPressed: () async {
// 보상형 광고 보여주기
var rewardedAds = await GoogleAdMob.showRewardedAd();
// 광고 보상 지급 완료 메시지
if (rewardedAds) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Text('보상이 지급되었습니다.'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(); // 다이얼로그 닫기,
},
child: Text('닫기'),
),
],
);
},
);
}
},
),
// 로그아웃 기능
CupertinoButton(
padding: EdgeInsets.zero,
child: const Icon(
CupertinoIcons.arrowshape_turn_up_left,
size: 30,
),
onPressed: () {
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: const Text('알림'),
content: const Text('로그아웃하시겠습니까?'),
actions: <CupertinoDialogAction>[
CupertinoDialogAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(context),
child: const Text('아니오'),
),
CupertinoDialogAction(
isDestructiveAction: true,
onPressed: () async {
SharedPreferences prefs =
await SharedPreferences.getInstance();
await prefs.remove('token');
Navigator.push(
context,
MaterialPageRoute(
builder: (builder) => LoginMainPage(),
),
);
},
child: Text('예'),
),
],
),
);
},
),
],
),
),
// 본문은 바텀 내비게이션 바의 인덱스에 따라 페이지 전환
body: _navIndex.elementAt(_selectedIndex),
// 바텀 내비게이션 바
bottomNavigationBar: BottomNavigationBar(
fixedColor: Colors.blue,
unselectedItemColor: Colors.blueGrey,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
items: const [
// BottomNavigationBarItem(
// icon: Icon(Icons.home_filled),
// label: '홈',
// backgroundColor: Colors.white,
// ),
BottomNavigationBarItem(
icon: Icon(Icons.my_library_books_outlined),
label: '메모',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.chat_bubble_2),
label: '커뮤니티',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_outline),
label: '내 정보',
),
],
currentIndex: _selectedIndex,
onTap: _onNavTapped,
),
);
}
}
참고
- 스파르타코딩클럽 플러터 앱개발 5주차
- Google AdMob - Mobile Ads SDK (Flutter)
- Google AdMob - Mobile Ads SDK (Android) - 테스트 광고 사용 설정
- Google AdMob - Mobile Ads SDK (iOS) - 테스트 광고 사용 설정
- Google AdMob - Mobile Ads SDK (Flutter) - 배너 광고
- Google AdMob - Mobile Ads SDK (Flutter) - 전면 광고
- Google AdMob - Mobile Ads SDK (Flutter) - 보상형 광고
- Google AdMob - Mobile Ads SDK (Flutter) - 네이티브 광고
- flutter - pub.dev - package - google_mobile_ads
- Google AdMob - 고객센터 - 일반적인 온보딩 문제 해결
- codelabs.developers.google.com - Flutter 앱에 AdMob 광고 추가
반응형