ํ๋ฌํฐ ์น(Flutter Web)์์ ๊ตฌ๊ธ ์ ๋์ผ์ค(AdSense) ๊ด๊ณ ๋ฅผ ์ ์ฉํ๋ ๋ฐฉ๋ฒ๊ณผ, ์ค์ ๋ก ๊ฒช์ด๋ณธ ์ค๋ฌด์ ์ด์, ๊ทธ๋ฆฌ๊ณ ์ฃผ์ํด์ผ ํ ์ ์ ๋ํด ์ ๋ฆฌํด๋ณธ๋ค.
์ผ๋จ ์ฃผ๋ก ์๋๋ก์ด๋์ iOS ๋ฑ ํด๋ผ์ด์ธํธ ์ฑ ๊ฐ๋ฐ์ ์ค์ฌ์ผ๋ก ํด์์ผ๋, ํ๋ฌํฐ๋ฅผ ์ด์ฉํ ์น ๊ฐ๋ฐ ๋ฐ ๊ด๊ณ ์ ์ฉ ํ ์คํธ๋ฅผ ์ง์ ์งํํด๋ดค๊ธฐ์, ์ด ๊ฒฝํ์ด ๋น์ทํ ๊ณ ๋ฏผ์ ํ๋ ๋ถ๋ค์๊ฒ ๋์์ด ๋๊ธธ ๋ฐ๋๋ค.
ํ๋ฌํฐ๋ก ์น ์๋น์ค, ์ค์ ์ค๋ฌด ์ ์ฉ์ ๊ฐ๋ฅํ ๊น?
ํ๋ฌํฐ(Flutter)๋ก ์น ์๋น์ค๋ฅผ ๊ฐ๋ฐํ ์ ์์์ง ๊ณ ๋ฏผํ๊ฒ ๋ ๊ณ๊ธฐ๋, ๊ตฌ๊ธ์ ํฌ๋ก์คํ๋ซํผ ๊ฐ๋ฐ ๋๊ตฌ๋ก์์ ์ฅ์ ๋๋ฌธ์ด์๋ค. ํ์ง๋ง ์ค์ ๋ก ์ ์ฉํด๋ณด๋, ํ๋ฌํฐ ์น์ ๋ก๋ฉ ์๋, ์๋ฌ ํธ๋ํน์ ๋ถํธํจ ๋ฑ ์ฌ๋ฌ ํ๊ณ์ ์ด ๋ถ๋ช ์กด์ฌํ๋ค. ๊ธฐ์กด์ ์ด ๊ธ ์ฐธ๊ณ
ํ์ฌ ์ด์ ํค์๋๋ฅผ ํ์ฉํด ์ ํ๋ธ ์์ธ (Shorts) ์์์ ๋ ธ์ถํ๋ ์น์ ํ๋ฌํฐ๋ก ์ ์ํ์ฌ ์ด์ ์ค์ด๋ค.
์ฌ์ค ์ฒ์๋ถํฐ ๊ด๊ณ ๋ฅผ ๋ถ์ด๋ ค๋ ๊ฑด ์๋์์ผ๋, ‘ํ๋ฌํฐ ์น์์๋ ๊ด๊ณ ์ ์ฉ์ด ์ด๋ ต๋ค’๋ ์ด์ผ๊ธฐ๋ฅผ ๋ค์๋ ํฐ๋ผ, ์ง์ ์๋ํด๋ณด๊ณ ๊ฒฝํ์ ๊ณต์ ํ๋ ๊ฒ ์๋ฏธ์๊ฒ ๋ค๊ณ ํ๋จํ๋ค.
๊ตฌ๊ธ ์ ๋์ผ์ค ์น์ธ, ๊ทธ๋ฆฌ๊ณ ์๋ธ๋๋ฉ์ธ ์ ์ฉ
์ ๋์ผ์ค(AdSense) ์น์ธ์ ์ด๋ฏธ ๋ฉ์ธ ๋๋ฉ์ธ์ผ๋ก ์ ์ฉํ ์ํ์ด๋ค. ์ฐธ๊ณ ๋ก, ์๋์ผ์ค๋ ๋ฉ์ธ ๋๋ฉ์ธ์ผ๋ก ์น์ธ์ ๋ฐ์ ๊ฒฝ์ฐ ์๋ธ๋๋ฉ์ธ์๋ ๋ณ๋์ ์น์ธ ์์ด ๊ด๊ณ ์ ์ฉ์ด ๊ฐ๋ฅํ๋ค.
์ฌ๊ธฐ์๋ ์ด๋ฏธ ์น์ธ์ ๋ฐ์ ์ํฉ์ ์ ์ ๋ก, ์ค์ ์ ์ฉ ๊ณผ์ ์ ์๊ฐํ๋ค.
์๋์ผ์ค ์น์ธ ํ๋ ๋ฐฉ๋ฒ ๋ฐ ๋ ธํ์ฐ๋ ๋ง์ ์๋ฃ๋ค์ ์ฐพ์๋ณด๋ฉด ์์ด์ ์ฌ๊ธฐ์๋ ๋ ผ์ธ๋ก ํ๋ค.
ํ๋ฌํฐ ์น์์ ๊ตฌ๊ธ ์ ๋์ผ์ค ์ ์ฉ์ด ์ด๋ ค์ด ์ด์
ํ๋ฌํฐ ์น์ SPA(Single Page Application) ๊ตฌ์กฐ๋ก ๋์ํ๋ฉฐ, ๋ชจ๋ UI๊ฐ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ๋์ ์ผ๋ก ๋๋๋ง๋๋ค.
์ด ๋๋ฌธ์ ๊ตฌ๊ธ ์ ๋์ผ์ค ๊ด๊ณ ์ฝ๋๋ฅผ ์ฝ์ ํด๋ ๋ฐ๋ก ๊ด๊ณ ๊ฐ ๋ ธ์ถ๋์ง ์๊ฑฐ๋, ์์ ์ ๋ณด์ด๋ ํ์์ด ๋น๋ฒํ๊ฒ ๋ฐ์ํ๋ค.๊ฒ๋ค๊ฐ ์ ์ HTML์ด ์๋๊ธฐ ๋๋ฌธ์, ๊ด๊ณ ๊ฐ ์ ๋ณด์ผ ๋ ์ด์์ ์์ธ์ ํธ๋ํนํ๊ธฐ๋ ์ฝ์ง ์๋ค.
๊ด๋ จ ์ ๋ณด๋ ํด๊ฒฐ ์ฌ๋ก๋ ๋ง์ง ์์์, ChatGPT์ ๊ฐ์ AI ๋๊ตฌ์ ๋์์ ๋ฐ์๋ ์ค์ ๊ด๊ณ ๊ฐ ๋ ธ์ถ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ง์๋ค.
์ฑ(AdMob),Google IMA ๊ฐ์ ๊ฒ๊ณผ ๋น๊ต
๋ชจ๋ฐ์ผ ์ฑ์ ๊ฒฝ์ฐ ๊ตฌ๊ธ AdMob์ SDK๋ก ์ฐ๋ํ๋ ๊ฒ ํ์ค์ด๋ค. ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์๋ต ๋ฉ์์ง๋ก ์๋ด๊ฐ ๋์ค๊ธฐ ๋๋ฌธ์ ๋๋ฒ๊น ๋ ์๋์ ์ผ๋ก ์ฝ๋ค.
๋ฐ๋ฉด, ์น(ํนํ ํ๋ฌํฐ ์น)์ SPA ๊ตฌ์กฐ์ ํ๊ณ๋ก ์ธํด ๊ด๊ณ ์ ์ฉ๊ณผ ๋๋ฒ๊น ๋ชจ๋ ๊น๋ค๋กญ๋ค.
๊ตฌ๊ธ ์ ๋์ผ์ค ๊ด๊ณ ํ๊ทธ ์์ฑ
๊ด๊ณ ์ ์ฉ์ ์ํด ์ ๋์ผ์ค์ ์ ์ํด ‘๋์คํ๋ ์ด ๊ด๊ณ ’ ๋จ์๋ฅผ ์์ฑํ๋ค.๊ด๊ณ ๋ ’๋ฐ์ํ(Responsive)’์ผ๋ก ๋ง๋ค์๊ณ , ์์ฑ ํ ์ ๊ณต๋๋ HTML ํ๊ทธ๋ฅผ ๋ณต์ฌํ๋ค.
๊ด๊ณ ํด๋ฆญ -> ๊ด๊ณ ๋จ์ ๊ธฐ์ค -> ๋์คํ๋ ์ด ๊ด๊ณ -> ์์ฑ
ํ๋ฌํฐ ์น์์ ์ ๋์ผ์ค ๊ด๊ณ ์์ ฏ ๋ง๋ค๊ธฐ
ํ๋ฌํฐ ์น์์ ์ ์ฉํด ๋ณด๊ธฐ ์ํด gpt ๋ฅผ ์ด์ฉํด์ ์ฝ๋๋ฅผ ์์ฑํด๋ณด์.
import 'dart:html';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
class AdsenseBannerWidget extends StatefulWidget {
final String adClient; // ex: "ca-pub-xxxx..."
final String adSlot; // ex: "1234567890"
final double width;
final double height;
const AdsenseBannerWidget({
Key? key,
required this.adClient,
required this.adSlot,
this.width = 320,
this.height = 100,
}) : super(key: key);
@override
State createState() => _AdsenseBannerWidgetState();
}
class _AdsenseBannerWidgetState extends State {
final String _viewType = 'adsense-html-banner';
@override
void initState() {
super.initState();
if (kIsWeb) {
// ๋ฐ๋์ // ignore: undefined_prefixed_name ๋ถ์ฌ ์ฌ์ฉ
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'$_viewType-${widget.adSlot}',
(int viewId) {
// ๊ด๊ณ ์ฝ๋ HTML
final htmlString = '''
''';
final element = DivElement()
..setInnerHtml(
htmlString,
validator: NodeValidatorBuilder()..allowElement('script')..allowElement('ins'),
);
// ๊ด๊ณ ์คํฌ๋ฆฝํธ๊ฐ ์์ผ๋ฉด ์ถ๊ฐ (์ค๋ณต๋ก๋ ๋ฐฉ์ง)
if (document.querySelectorAll('script[src*="pagead2.googlesyndication.com"]').isEmpty) {
final script = ScriptElement()
..async = true
..src = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${widget.adClient}"
..setAttribute('crossorigin', 'anonymous');
document.head!.append(script);
}
return element;
},
);
}
}
@override
Widget build(BuildContext context) {
return
Container(color: Colors.red, child:SizedBox( width: widget.width,height: widget.height));
}
}
์ด๋ ๊ฒ ์์ ฏ์ ๋ง๋ค์ด์ ๊ด๊ณ ๋ฅผ ๋ ธ์ถ์ํค๋ ค ํ์ผ๋, ์ค์ ๋ก๋ ๊ด๊ณ ๊ฐ ๋ ธ์ถ๋์ง ์์๋ค.
๊ด๊ณ ๋ ์๋ ๊ทธ๋ฆผ ์ฒ๋ผ AppTopBar ํ๋จ์ ํ๋ ค๊ณ ํ๋ค.
์ ๋์ผ์ค ํ๋ฌ๊ทธ์ธ/ํจํค์ง ํ์
ํน์ ๊ธฐ์กด์ ๋๊ตฐ๊ฐ ๋ง๋ ํ๋ฌ๊ทธ์ธ์ด๋ ํจํค์ง๊ฐ ์๋์ง pub.dev์์ ๊ฒ์ํด๋ดค์ผ๋, ์ ์ง ๊ด๋ฆฌ๊ฐ ๋์ง ์๊ฑฐ๋ ์ ๋๋ก ๋์ํ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ค.
https://pub.dev/packages? q=platform%3 Aweb+adsense&sort=downloads
๊ฒฐ๊ตญ ์ง์ ๊ตฌํํด์ ํด๊ฒฐํ๋ ์ชฝ์ผ๋ก ๋ฐฉํฅ์ ์ก์๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ: Iframe์ผ๋ก ๊ด๊ณ HTML ๋ก๋ฉ
ํ๋ฌํฐ ์น์์ ๊ด๊ณ ๋ฅผ ํ์คํ๊ฒ ๋ ธ์ถํ๋ ค๋ฉด, ๋ณ๋์ HTML ํ์ผ(์: adsense_banner.html)์ ์์ฑํด ์์ (assets) ํด๋์ ์ถ๊ฐํ๊ณ , ์ด๋ฅผ iframe์ผ๋ก ๋ก๋ฉํ๋ ๋ฐฉ์์ผ๋ก ํด๊ฒฐ ํ์๋ค
์ค์ ๊ด๊ณ ๊ฐ ๋ ธ์ถ๋๋ ๊ฒ์ ํ์ธํ ์ ์์๊ณ , ํนํ ๊ด๊ณ ์/์๋ ๋ ์ด์์์ด ๊นจ์ง์ง ์๊ฒ ํ๋ ค๋ฉด ๋์ด๋ฅผ 70px ๋ก ๊ณ ์ ํด ์ฃผ์๋ค. 70px ๋ ๋ฐ์ํ์ด ์๋๊ฑธ๋ก ์ถ๊ฐ ํ๋ คํ ๋ ์ ์ผ ํฐ ๋์ด๋ผ ์ผ๋จ 70px ๋ก ํ์๋ค.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:html' as html;
class AdsenseBannerAssetWidget extends StatefulWidget {
final double width;
final double height;
const AdsenseBannerAssetWidget({
Key? key,
this.width = 400,
this.height = 100,
}) : super(key: key);
@override
State createState() => _AdsenseBannerAssetWidgetState();
}
class _AdsenseBannerAssetWidgetState extends State {
late final String _viewType;
@override
void initState() {
super.initState();
_viewType = 'adsense-banner-iframe-${widget.width}-${widget.height}-${UniqueKey()}';
if (kIsWeb) {
// ignore: undefined_prefixed_name (ํ๋ฌํฐ ๋ฒ์ ์ ๋ฐ๋ฅธ ๋ฌด์)
ui.platformViewRegistry.registerViewFactory(
_viewType,
(int viewId) {
//iframe ์ผ๋ก ๋ฆฌํด
final iframe = html.IFrameElement()
..width = widget.width.toInt().toString()
..height = widget.height.toInt().toString()
..src = '/assets/adsense_banner.html'
..style.border = 'none';
return iframe;
},
);
}
}
@override
Widget build(BuildContext context) {
if (!kIsWeb) return const SizedBox();
return SizedBox(
width: widget.width,
height: widget.height,
child: HtmlElementView(
viewType: _viewType,
),
);
}
}
6. ํ ์คํธ์ ๋ฐฐํฌ
ํ ์คํธํ ๋๋ ๊ด๊ณ ๊ฐ ์ ์์ ์ผ๋ก ๋ ธ์ถ๋๋์ง, ํด๋ฆญ ์ด๋ฒคํธ๊ฐ ์ ์ ๋์ํ๋์ง๊น์ง ๊ผญ ์ฒดํฌํด์ผ ํ๋ค.
ํนํ ์๋์ผ์ค๋ ‘ํ ์คํธ ๊ด๊ณ ’๋ฅผ ๊ณต์์ ์ผ๋ก ์ ๊ณตํ์ง ์์ผ๋ฉฐ, ์ค ์๋น์ค ๋๋ฉ์ธ์์๋ง ๊ด๊ณ ๊ฐ ์ ์์ ์ผ๋ก ํ์๋๋ค.
๋ก์ปฌํ๊ฒฝ์์๋ ๊ด๊ณ ๊ฐ ๋น ์์ญ์ผ๋ก๋ง ๋์ค๋, ์ค์ ๋ฐฐํฌ ์ ์๋ ๋ฐ๋์ ๋ฐฐํฌ์ฉ ๋๋ฉ์ธ์์ ์ต์ข ํ์ธ์ ํด์ผ ํ๋ค.
์๋ ์ฝ๋๋ ์๋์ผ์ค ์ํ ์๋์ง๋ง ์ผ๋จ ํด๋ฆญ์ด๋ฒคํธ๊ฐ ์ ๋จนํ๋์ง ํ ์คํธ ํด๋ณผ ์์๋ html ์ด๋ค.
์๋ ์ฒ๋ผ ์ ์ ํ ๊ณณ์ ํด๋น ์์ ์ ์คํํ๋ค.
Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: EdgeInsets.only(top:_topBarHeight),
width: double.infinity,
height: 70,
child: AdsenseBannerAssetWidget(
width: MediaQuery.of(context).size.width,
height: 70
),
),
videoview widget..
]
.....
๊ฒฝํ ๊ธฐ๋ฐ ๊ฒฐ๋ก ๋ฐ ์ฃผ์์ฌํญ
-
ํ๋ฌํฐ ์น์ ๊ด๊ณ ์ ์ฉ์ด ์ฝ์ง ์๋ค. SPA ๊ตฌ์กฐ, ์๋ฐ์คํฌ๋ฆฝํธ ๋๋๋ง ํน์ฑ ๋๋ฌธ์ ์ ๋์ผ์ค๊ฐ ๋ฐ๋ก ์ ์ฉ๋์ง ์๋๋ค.
-
Iframe ๋ฐฉ์์ด ํ์ค์ ์ธ ๋์์ด๋ค. ๋ณ๋์ HTML์ ๋ง๋ค์ด assets๋ก ์ถ๊ฐํ๊ณ , ์ด๋ฅผ iframe์ผ๋ก ๋ก๋ฉํ๋ ๋ฐฉ๋ฒ์ด ๊ฐ์ฅ ๊น๋ํ๋ค.
-
ํ ์คํธ ๊ด๊ณ ๋ ๋ฐ๋ก ์ ๊ณต๋์ง ์๋๋ค. ๋ถ์ ํด๋ฆญ ์ฃผ์(์ง์ ํด๋ฆญ X)
-
๋ก์ปฌ์์๋ ๊ด๊ณ ๊ฐ ์ ๋์ด. ์ค ์๋น์ค ๋๋ฉ์ธ์์๋ง ์ ์ ๋์
-
ํ๋ฌํฐ ์น์ ์ค๋ฌด/์์ฉ ์๋น์ค์ ์ ํฉํ์ง ์์ ์ ์์. MVP, ํ๋กํ ํ์ ์ ๋๋ก๋ ์ธ๋งํ๋, ๋๋ฆฐ ์๋, ํ๊ธ ํฐํธ ๊นจ์ง, ๋๋ฒ๊น ์ด๋ ค์ ๋ฑ ํ๊ณ๊ฐ ๋ช ํํ๋ค.
-
์ ๋์ผ์ค ๊ด๊ณ ๋จ์/์์น๋ ์ ์คํ๊ฒ ๊ฒฐ์ . ์ต์ปค ๊ด๊ณ (์/ํ๋จ) ๊ฒน์นจ ๋ฑ ์ด์ ์ฃผ์.
๋ง์น๋ฉฐ
๊ฒฝํํ ํ๋ฌํฐ ์น์์ ๊ตฌ๊ธ ์ ๋์ผ์ค ๊ด๊ณ ์ ์ฉ ๋ฐฉ๋ฒ๊ณผ ๋ฌธ์ ํด๊ฒฐ ์ฌ๋ก๋ฅผ ๊ณต์ ํ๋ค.
ํน์๋ ์๋์ผ์ค ๊ด๊ณ ๋ฅผ ์ ์ฉํ๋ ค ๊ณ ๋ฏผํ๋ ๊ฐ๋ฐ์ ๋ถ๋ค์๊ฒ ์ค์ ๋ก ์ ์ฉ ๊ฐ๋ฅํ ํด๋ฒ์ด ๋๊ธธ ๋ฐ๋๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก, ํ๋ฌํฐ ์น์ ์์ง ์น ์ค๋ฌด์์๋ ๋น์ถ์ฒ์ด์ง๋ง, ๋ถ๊ฐํผํ๊ฒ ๊ด๊ณ ๊น์ง ๋ถ์ฌ์ผ ํ๋ค๋ฉด iframe ๋ฐฉ์์ผ๋ก ํด๊ฒฐ ํ๊ณ ์ ์ ์ ์ผ๋ก ์งํ๊ฐ ๋์ค๋์ง ์ง์์ ์ผ๋ก ๋ชจ๋ํฐ๋ง์ ํด๋ณด๋ ค ํ๋ค.
๋๊ธ ์ฐ๊ธฐ