torikatsu.dev

Flutterとかプログラミングとかガジェットとか書きます

Flutterのネイティブ広告をKotlinで実装する

はじめに

先日、Google Mobile Adsのflutterプラグインが発表されました。 developers.google.cn

先祖のプラグインにあたるAdmobは非常に使いづらかった印象だったので、ついにきたか!と非常にわくわくしました。 早速実装をしようと以下の公式docを参照してみたのですが、ネイティブ広告のAndroidのサンプルコードがjavaでした。

私が開発中のプロダクトはKotlinを採用しているので、公式サンプルをKotlinに読み替えつつ、ネイティブ広告の実装方法を共有したいと思います。

サンプル

今回紹介するサンプルは以下のリポジトリです。 https://github.com/torikatsupg/googleads_mobile_sdk_sample

ネイティブ広告以外の広告のサンプルもあります。もし何かの参考になれば幸いです。

実装

下準備

依存関係の追加

まずはパッケージの依存関係をpubspec.yamlに追加します。 pub.devのinstallingを参考に、適宜バージョンを読み替えてください。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
+  google_mobile_ads: ^0.11.0+1

Admob IDの追加

次にAdmobのIDをAndroidManifest.xmlに追加する必要があります。

今回はサンプルの実装なので以下のIDを使用します。

ca-app-pub-3940256099942544~3347511713 

適宜各自のIDに読み替えてください。

android/main/src/AndroidManifest.xml

<manifest>
    <application>

        .........

+        <meta-data
+            android:name="com.google.android.gms.ads.APPLICATION_ID"
+            android:value="ca-app-pub-3940256099942544~3347511713 "/>
    <application>
<manifest>

SDKの初期化

最後にアプリ起動時にSDKの初期化をするようにします。 SDKの初期化は以下によって行います。

MobileAds.instance.initialize();

公式docによると初期化のタイミングはアプリ起動前がいいそうです。 今回は、run()で初期化します。

run.dart

import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:googleads_mobile_sdk_sample/app.dart';

run() {
  WidgetsFlutterBinding.ensureInitialized();
+  MobileAds.instance.initialize();

  return runApp(
    ProviderScope(
      child: App(),
    ),
  );

ネイティブ広告(Android)を実装する

ネイティブ広告の実装はざっくりとFlutter側の実装とネイティブ側の実装に別れます。

Flutter側の実装

ネイティブ広告は、Flutter側でウィジェットとして扱います。 なので、広告ウィジェットを作成する関数を定義し、UIに広告を組み込みます。

初めにlistenerを作成します。 とりあえず表示したいだけなら、広告の読み込みが完了したときのonAdLoadedと、onAdClosedでのリスナの開放ぐらいを実装しておけばいいと思います。

native_page.dart

  Widget _buildNativeAd() {
    final listener = AdListener(
      onAdLoaded: (Ad ad) => ,
      onAdFailedToLoad: (Ad ad, LoadAdError error) {
        print('Ad failed to load: $error');
      },
      onAdOpened: (Ad ad) => print('Ad opened.'),
      onAdClosed: (Ad ad) => print('Ad closed.'),
      onApplicationExit: (Ad ad) => print('Left application.'),
      onNativeAdClicked: (NativeAd ad) => print('Ad clicked.'),
      onNativeAdImpression: (NativeAd ad) => print('Ad impression.'),
    );

   ......

listenerの作成が終わったら、広告を作成します。 広告の作成には以下の4つを渡してあげる必要があります。

  • 広告ユニットID 今回はサンプルとして用意されたca-app-pub-3940256099942544/2247696110を使用します。
  • このあとAndroid側で登録する任意のFactoryID(文字列)
  • AdRequestインスタンス
  • 先ほどつくったリスナ

実装は以下のようになります。

native_page.dart

  ......

  final myNative = NativeAd(
      adUnitId: 'ca-app-pub-3940256099942544/2247696110',
      factoryId: 'adFactoryExample',
      request: AdRequest(),
      listener: listener,
    );
    myNative.load();
    return AdWidget(ad: myNative);

広告の実装ができたら、広告の読み込みを開始しつつAdWidgetに広告を渡して、広告ウィジェットを作成します。

native_page.dart

  ......

  myNative.load();
  return AdWidget(ad: myNative);
}

Flutter側の実装は以上になります。

ネイティブ側での実装

初めに広告のUIを作成します。 Androidでは、UIをxml形式で記述してきます。 ここでandroid:id="...のように、Viewの要素にIDがいくつか指定されていることがわかります。 あとでこのIDをレイアウトにアクセスするときに使用します。

android/src/main/res/layout/my_native_ad.xml

<com.google.android.gms.ads.formats.UnifiedNativeAdView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/ad_headline"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/ad_body"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>
</com.google.android.gms.ads.formats.UnifiedNativeAdView>

次にNativeAdFactoryを継承した、広告を作成するクラスを定義します。 今回はNativeAdFactoryExampleというクラス名にします。

android/src/main/kotlin/com/example.googleads_mobile_sdk_sample/NativeAdFactoryExample.kt

class NativeAdFactoryExample(private val layoutInflater: LayoutInflater) : NativeAdFactory {

    override fun createNativeAd(nativeAd: UnifiedNativeAd?,
                                customOptions: MutableMap<String, Any>?): UnifiedNativeAdView {
        val adView = layoutInflater.inflate(R.layout.my_native_ad, null)
                as UnifiedNativeAdView
        val headlineView = adView.findViewById<TextView>(R.id.ad_headline)
        val bodyView = adView.findViewById<TextView>(R.id.ad_body)

        headlineView.text = nativeAd?.headline
        bodyView.text = nativeAd?.body

        adView.setBackgroundColor(Color.YELLOW)

        adView.setNativeAd(nativeAd)
        adView.headlineView = headlineView
        adView.bodyView = bodyView
        return adView
    }
}

ここでやっていることは、レイアウトのxmlを読み込んできて、xmlに定義したID(android:id="...)を頼りに、各コンテンツを埋め込んで広告のUIを作成するといった感じです。

次に、Flutter側で定義したFactoryIDと、先ほど作成したFactoryクラスの紐付けを行います。

android/src/main/kotlin/com/example/googleads_mobile_sdk_sample/MainActivity.kt

class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        flutterEngine.plugins.add(GoogleMobileAdsPlugin());
        super.configureFlutterEngine(flutterEngine)

+        GoogleMobileAdsPlugin.registerNativeAdFactory(
+                flutterEngine,
+                "adFactoryExample",
+                NativeAdFactoryExample(layoutInflater)
+        )

    }

```GoogleMobileAdsPlugin.registerNativeAdFactory```の引数に、Flutter側で定義した```adFactoryExample```というFactoryIDと、先ほど作成した```NativeAdFactoryExample```を渡しているのがわかります。

さらに、アプリ終了時に広告の削除処理を行うために```cleanUpFlutterEngine```を以下のようにオーバーライドします。

android/src/main/kotlin/com/example/googleads_mobile_sdk_sample/MainActivity.kt

......

override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
    super.cleanUpFlutterEngine(flutterEngine)
    GoogleMobileAdsPlugin.unregisterNativeAdFactory(
            flutterEngine, "adFactoryExample"
    )
}
これでネイティブ広告を表示できるようになりました!👏

[f:id:torikatsu923:20210413010107p:plain]

## おわりに
今回はKotlinでネイティブ広告を実装する方法を紹介しました。
本当はIOS側の実装をswiftに読み替えて、一緒に紹介する予定でした。しかし、IOS開発やobjective-cに詳しくないために、ランタイムエラーを回避することができず断念しました。
サンプルをみていて気づいたことがありましたらissueをあげていただけると嬉しいです。

それでは良い開発ライフを!