Flutterでネイティブ広告(ネイティブアドバンス)を使ってみる(Android)
はじめに
自分はグリッドビューを使っていてその中で広告を表示させようとしていました。今までバナー広告を使っていたのですが、グリッドビューだと一つのグリッドが縦長になってしまうからかうまく広告が表示されませんでした。そこでいろいろ調べてみて、カスタマイズ性が高い?ネイティブ広告ならなんとかできるのかなと思い実装してみました。
元々NativeAdの方にはnativeTemplateStyleというものがあり、これを使うのでもいいとは思うのですが、どうせならカスタマイズする方法を知りたいということで調べました。
自分はネイティブ(KotlinとかSwift)に触ったことがないのとAndroid StudioでなくVSCodeで開発しているからか、思ったより面倒だったので備忘録としてまとめておきます。
あまりわかっていない部分があるので参考程度にしてください。
間違っているところだったり改善点だったりがあり、かつ暇であれば指摘をお願いします。
以下では、すでにGoogle Admobの方でネイティブアドバンス広告のユニットIDは取得済みとします。
環境
Flutter 3.19.5
google_mobile_ads: ^5.0.0
公式など参考
Codelabs
ここが一番詳しいと思いますがAndroid Studioを使わないのでディレクトリがよくわからなかったです。これはCodelabsのまとめ的なものでディレクトリがわかります。
ここも公式だとは思いますが、ちょいちょい違うのでよくわかりませんでした。settings.gradleとか。
基本的に上二つを参考にして、layoutを決めるxmlファイルは頑張って決める感じです。ChatGPTとかに聞く感じで。
ディレクトリ
layoutを決めるxmlファイルについて
MainActivityとFactoryを入れるディレクトリ(今回見せるのはLightFactoryとMainActivityのみ)
公式では、
list_tile_native_ad.xml、ListTileNativeAdFactory.kt、MainActivity.kt、となっているファイルを自分好みに編集して、Flutterの方で使います。
自分の場合、前者二つを、grid_native_ad_light.xml、GridNativeAdLightFactory.ktという名前にして編集しました。
ネイティブでの編集
grid_native_ad_light.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.gms.ads.nativead.NativeAdView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_grid_native_ad_light_icon"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitCenter"
tools:background="#EDEDED"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHeight_percent="0.6"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/tv_grid_native_ad_light_attribution_small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#F19938"
android:text="Ad"
android:textColor="#FFFFFF"
android:padding="2dp"
android:textSize="14sp"
android:layout_marginTop="2dp"
app:layout_constraintTop_toBottomOf="@id/iv_grid_native_ad_light_icon"
/>
<TextView
android:id="@+id/tv_grid_native_ad_light_attribution_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#F19938"
android:gravity="center"
android:text="Ad"
android:textColor="#FFFFFF"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/tv_grid_native_ad_light_attribution_small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/tv_grid_native_ad_light_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:textColor="#007BFF"
android:textSize="16sp"
tools:text="Headline" />
<TextView
android:id="@+id/tv_grid_native_ad_light_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:textColor="#828282"
android:textSize="14sp"
tools:text="body" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.gms.ads.nativead.NativeAdView>
正直なところレイアウトはよくわかっていないのですが上記のように自分はしています。
ImageViewで画像を上の方に表示し親要素、ここではグリッドの高さの60%を占めるようにしています。
また広告であることを示すための「Ad」はImageViewのIdを取ってその左下に置くようにしています。
後はその下に説明文を載せている感じです
ここが一番面倒でした。
以下のGridNativeAdLightFactory.ktで参照します。
GridNativeAdLightFactory.kt
package com.example.hogehoge
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 GridNativeAdLightFactory(val context: Context) : GoogleMobileAdsPlugin.NativeAdFactory {
override fun createNativeAd(
nativeAd: NativeAd,
customOptions: MutableMap<String, Any>?
): NativeAdView {
// val nativeAdView = LayoutInflater.from(context)
// .inflate(R.layout.grid_native_ad_light, null) as NativeAdView
val nativeAdView = LayoutInflater.from(context)
.inflate(R.layout.grid_native_ad_light, null, false) as NativeAdView
with(nativeAdView) {
val attributionViewSmall =
findViewById<TextView>(R.id.tv_grid_native_ad_light_attribution_small)
val attributionViewLarge =
findViewById<TextView>(R.id.tv_grid_native_ad_light_attribution_large)
val iconView = findViewById<ImageView>(R.id.iv_grid_native_ad_light_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_grid_native_ad_light_headline)
headlineView.text = nativeAd.headline
this.headlineView = headlineView
val bodyView = findViewById<TextView>(R.id.tv_grid_native_ad_light_body)
with(bodyView) {
text = nativeAd.body
visibility = if (nativeAd.body.isNullOrEmpty()) View.INVISIBLE else View.VISIBLE
}
this.bodyView = bodyView
setNativeAd(nativeAd)
}
return nativeAdView
}
}
ここでgrid_native_ad_light.xmlファイルを参照しています。
MainActivity.kt
package com.example.hogehoge
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()
元々上記であったファイルを
package com.example.hogehoge
import android.content.Context
import android.view.LayoutInflater
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val gridNativeLightFactory = GridNativeAdLightFactory(context)
GoogleMobileAdsPlugin.registerNativeAdFactory(flutterEngine, "gridLight", gridNativeLightFactory)
}
override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
super.cleanUpFlutterEngine(flutterEngine)
// Factoryを登録解除
GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, "gridLight")
}
}
のように変更しました。ここで先ほど作成したGridNativeAdLightFactory.ktを使っているんですね。
Flutter側での処理
import 'package:your_project/env/env.dart';
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
class GridNativeAdWidget extends StatefulWidget {
final double height;
final double width;
const GridNativeAdWidget({
super.key,
required this.height,
required this.width,
});
_GridNativeAdWidgetState createState() => _GridNativeAdWidgetState();
}
class _GridNativeAdWidgetState extends State<GridNativeAdWidget> {
NativeAd? _nativeAd;
bool _isAdLoaded = false;
void _loadNativeAd() {
_nativeAd = NativeAd(
adUnitId: Env.n1, // 広告ユニットID、自分はenviedを使っている
//factoryIdを指定しない場合にはnativeTemplateStyleを使う。
factoryId: 'gridLight', // MainActivity.ktで指定したfactoryId
//アスペクト比
nativeAdOptions: NativeAdOptions(
mediaAspectRatio: MediaAspectRatio.any,
),
listener: NativeAdListener(
onAdLoaded: (_) {
setState(() {
_isAdLoaded = true;
});
},
onAdFailedToLoad: (ad, error) {
ad.dispose();
},
),
request: const AdRequest(),
);
_nativeAd!.load();
}
void initState() {
super.initState();
_loadNativeAd();
}
void dispose() {
_nativeAd?.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
height: widget.height,
child: _isAdLoaded ? AdWidget(ad: _nativeAd!) : Container(),
);
}
}
重要な部分を抜き出します。
void _loadNativeAd() {
_nativeAd = NativeAd(
adUnitId: Env.n1, // 広告ユニットID、自分はenviedを使っている
//factoryIdを指定しない場合にはnativeTemplateStyleを使う。
factoryId: 'gridLight', // MainActivity.ktで指定したfactoryId
//アスペクト比
nativeAdOptions: NativeAdOptions(
mediaAspectRatio: MediaAspectRatio.any,
),
listener: NativeAdListener(
onAdLoaded: (_) {
setState(() {
_isAdLoaded = true;
});
},
onAdFailedToLoad: (ad, error) {
ad.dispose();
},
),
request: const AdRequest(),
);
_nativeAd!.load();
}
ここでfactoryIdを先ほどMainActivity.ktで書いた、gridLightに設定することで、自分の好きなlayoutを呼び出すことができます。
factoryIdを使わないのであればnativeTemplateStyleを使うこととなります。
どちらか一方は必ず非nullでないといけません。
実際にグリッドで表示すると次のようになりました。
グリッド1
グリッド2
グリッド3
あとがき
テンプレートを使えばある程度はいいのでしょうが、カスタマイズするにはどうすればいいのかを調べてみると結構面倒でした。とくにxmlファイルでレイアウトをどう制御するのかがあまりわからずに苦戦しました。
ネイティブについてはまだあまりよくわかっていないのでもう少し勉強したいです。
VSCodeの拡張機能とかでAndroid Studioのビューワーみたいなのってないんですかね?以下のサイトみたいな感じで
勉強はこれとかよさそう
Discussion