AdMob 광고 종류 비교
| 적응형 배너 (Adaptive Banner) | 고정 크기 배너 (Fixed Size Banner) |
| 디바이스 화면 크기에 맞춰 자동으로 크기가 조절된다 |
정해진 픽셀 값을 그대로 유지한다 |
![]() |
![]() |
| 네이티브 템플릿 광고 (Native Templates) | 네이티브 플랫폼 설정 광고 (Platform Setup) |
| 텍스트, 색상, 버튼 스타일 등을 커스텀할 수 있다 | XML 레이아웃 기반으로 완전한 커스텀이 가능하다 |
![]() |
![]() |
처음에는 간단하게 배너 광고를 추가했지만,
앱 전체의 UI에 자연스럽게 녹아들 수 있는 스타일을 원해서 네이티브 광고로 전환했다.
플러터에서는 두 가지의 네이티브 광고 방식을 제공하는데
이 중에서도 가장 자유도가 높은 플랫폼 네이티브 방식을 선택했다.
Flutter AdMob 연동 기초 설정
| 적응형 배너 (Adaptive Banner) | ca-app-pub-3940256099942544/9214589741 |
| 고정 크기 배너 (Fixed Size Banner) | ca-app-pub-3940256099942544/6300978111 |
| 네이티브 광고 | ca-app-pub-3940256099942544/2247696110 |
애드몹에서 광고 단위를 생성하면 앱 ID와 광고 단위 ID가 제공되는데
개발 단계에서는 반드시 테스트 광고 단위 ID를 사용해야 한다.
개발 단계에서 실제 광고 단위 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>
`android/app/src/main/AndroidManifest.xml`에 자신의 애드몹 앱 ID를 추가한다.
flutter pub add google_mobile_ads
플러터 앱에 애드몹 광고를 추가하기 위해 google_mobile_ads 패키지를 설치한다.
import 'package:flutter/foundation.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
// 배너 광고 유닛 ID
String admobBannerUnitId = kReleaseMode
? 'YOUR_ADMOB_BANNER_UNID_ID'
: 'ca-app-pub-3940256099942544/6300978111';
// 네이티브 광고 유닛 ID
String admobNativeUnitId = kReleaseMode
? 'YOUR_ADMOB_NATIVE_UNIT_ID'
: 'ca-app-pub-3940256099942544/2247696110';
void main() async {
// Flutter 초기화
WidgetsFlutterBinding.ensureInitialized();
// 구글 애드몹 초기화
MobileAds.instance.initialize();
runApp(const MyApp());
}
`main.dart`에서 애드몹 SDK 초기화하고,
디버그, 릴리즈 환경에 따라 광고 유닛 ID를 가져온다.
배너 광고 추가하기
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../main.dart';
/// 배너 광고 위젯
class BannerAdWidget extends StatefulWidget {
const BannerAdWidget({super.key});
@override
State<BannerAdWidget> createState() => _BannerAdWidgetState();
}
class _BannerAdWidgetState extends State<BannerAdWidget> {
BannerAd? _bannerAd;
AdSize? _adSize;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadAd();
});
}
@override
void dispose() {
_bannerAd?.dispose();
super.dispose();
}
// 광고 생성 및 로드
Future<void> _loadAd() async {
if (!mounted) return;
final screenWidth = MediaQuery.of(context).size.width.truncate();
// Padding 추가
final availableWidth = screenWidth - 42;
final size = await AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
availableWidth,
);
if (size == null) {
return;
}
if (mounted) {
setState(() {
_adSize = size;
});
}
BannerAd(
adUnitId: admobBannerUnitId,
request: const AdRequest(),
size: size,
listener: BannerAdListener(
onAdLoaded: (ad) {
if (mounted) {
setState(() {
_bannerAd = ad as BannerAd;
});
}
},
onAdFailedToLoad: (ad, err) {
debugPrint("Ad failed to load with error: $err");
ad.dispose();
},
),
).load();
}
@override
Widget build(BuildContext context) {
if (_adSize == null) {
return const SizedBox();
}
return SizedBox(
width: _adSize!.width.toDouble(),
height: _adSize!.height.toDouble(),
child: _bannerAd != null ? AdWidget(ad: _bannerAd!) : const SizedBox(),
);
}
}
적응형 배너와 고정 크기 배너를 구현할 때는 동일한 코드에 유닛 ID만 교체해 주면 된다.
배너 광고는 구현이 가장 간단하지만, 사이즈를 제외한 요소들을 커스텀 할 수 없다는 단점이 있다.
네이티브 템플릿 광고 추가하기
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../main.dart';
// 네이티브 템플릿 광고 위젯
class NativeTemplatesAdWidget extends StatefulWidget {
const NativeTemplatesAdWidget({super.key});
@override
State<NativeTemplatesAdWidget> createState() =>
_NativeTemplatesAdWidgetState();
}
class _NativeTemplatesAdWidgetState extends State<NativeTemplatesAdWidget> {
NativeAd? _nativeAd;
bool _nativeAdIsLoaded = false;
final double _adAspectRatioMedium = (370 / 355);
@override
void initState() {
super.initState();
_loadAd();
}
@override
void dispose() {
_nativeAd?.dispose();
super.dispose();
}
/// 네이티브 광고 로드
void _loadAd() async {
setState(() {
_nativeAdIsLoaded = false;
});
_nativeAd = NativeAd(
adUnitId: admobNativeUnitId,
listener: NativeAdListener(
onAdLoaded: (ad) {
debugPrint('$NativeAd loaded.');
if (mounted) {
setState(() {
_nativeAdIsLoaded = true;
});
}
},
onAdFailedToLoad: (ad, error) {
debugPrint('$NativeAd failedToLoad: $error');
ad.dispose();
},
),
request: const AdRequest(),
nativeTemplateStyle: NativeTemplateStyle(
templateType: TemplateType.small,
callToActionTextStyle: NativeTemplateTextStyle(
textColor: Colors.white,
style: NativeTemplateFontStyle.monospace,
size: 16.0,
),
primaryTextStyle: NativeTemplateTextStyle(
textColor: Colors.black,
style: NativeTemplateFontStyle.bold,
size: 16.0,
),
),
)..load();
}
@override
Widget build(BuildContext context) {
if (_nativeAdIsLoaded && _nativeAd != null) {
return SizedBox(
height: MediaQuery.of(context).size.width * _adAspectRatioMedium,
width: double.infinity,
child: AdWidget(ad: _nativeAd!),
);
}
return const SizedBox.shrink();
}
}
네이티브 템플릿 광고도 간단하게 구현할 수 있고, 기본적인 색상·텍스트 스타일 변경도 가능하다.
네이티브 플랫폼 설정 광고 추가하기
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.gms.ads.nativead.NativeAdView
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.nativead.NativeAdView>
`android\app\src\main\res\layout` 디렉토리에 `native_ad_custom.xml` 파일을 생성한다.
package com.example.name ✅패키지 이름 수정
import com.rainypoint.alarm.R
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.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdView
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin
class ListTileNativeAdFactory(val context: Context) : GoogleMobileAdsPlugin.NativeAdFactory {
override fun createNativeAd(
nativeAd: NativeAd,
customOptions: MutableMap<String, Any>?
): NativeAdView {
val nativeAdView = LayoutInflater.from(context)
.inflate(R.layout.native_ad_custom, null) as NativeAdView
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.isNullOrEmpty()) View.INVISIBLE else View.VISIBLE
}
this.bodyView = bodyView
setNativeAd(nativeAd)
}
return nativeAdView
}
}
`android\app\src\main\kotlin\com\example\name` 경로(본인의 패키지 이름)에 `ListTileNativeAdFactory.kt` 파일을 생성한다.
package com.example.name ✅패키지 이름 수정
import io.flutter.embedding.android.FlutterActivity
import com.rainypoint.alarm.ListTileNativeAdFactory
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GoogleMobileAdsPlugin.registerNativeAdFactory(
flutterEngine, "adFactoryExample", ListTileNativeAdFactory(context)
)
}
override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
super.cleanUpFlutterEngine(flutterEngine)
GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, "adFactoryExample")
}
}
`android\app\src\main\kotlin\com\example\name` 경로의 `MainActivity.kt` 파일을 수정한다.
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../main.dart';
class NativePlatformAdWidget extends StatefulWidget {
const NativePlatformAdWidget({super.key});
@override
State<NativePlatformAdWidget> createState() => _NativePlatformAdWidgetState();
}
class _NativePlatformAdWidgetState extends State<NativePlatformAdWidget> {
late NativeAd _ad;
bool isLoaded = false;
@override
void initState() {
super.initState();
_ad = NativeAd(
adUnitId: admobNativeUnitId,
factoryId: "adFactoryExample",
request: const AdRequest(),
listener: NativeAdListener(onAdLoaded: (ad) {
setState(() {
_ad = ad as NativeAd;
isLoaded = true;
});
}, onAdFailedToLoad: (ad, error) {
ad.dispose();
}),
);
_ad.load();
}
@override
void dispose() {
super.dispose();
_ad.dispose();
}
@override
Widget build(BuildContext context) {
if (isLoaded == true) {
return SizedBox(
height: 60,
width: double.infinity,
child: AdWidget(ad: _ad),
);
} else {
return const SizedBox(height: 0);
}
}
}
플랫폼 네이티브 광고는 네이티브 코드를 직접 수정해야 하지만,
아이콘 크기, 텍스트, 버튼, 배경, 표시 방식 등을 완전히 커스텀할 수 있기 때문에 앱 UI에 최적화된 형태로 적용할 수 있다.
구현 결과
![]() |
![]() |
![]() |
| Adaptive Banner | Platform Setup 기본 | Platform Setup UI 수정 |
네이티브 플랫폼 방식을 통해 광고를 UI 구성 요소의 일부처럼 보이도록 자연스럽게 배치함으로써
사용자 경험을 최대한 해치지 않기 위해 노력했다.






