Kivyで作ったAndroidアプリにKivMobでAdMobの広告を表示したときの試行錯誤、の巻

Kivyで作ったAndroidアプリにKivMobでAdMobの広告を表示したときの試行錯誤、の巻

はじめに

PythonのGUIフレームワークであるKivyを使ってAndroidアプリを作っていまして、AdMobの広告表示のためにKivMobというツールを使用しています。

で、いまさらになって組み込まれている広告用SDKをfirebase-ads 19.4.0から21.4.0に更新しようとしたのですが、どうやらGoogleのAdMob広告関連APIの仕様変更(1、2年前だけど)の影響を受けていて、KivMobでのインタースティシャル広告とリワード広告がエラーになって表示されないようになっていました。いやーん。(ちなみにバナー広告は表示できました。)

本記事は、KivMobにおいてfirebase-ads 21.4.0を組み込んでAdMobの広告を正常に表示する方法を試行錯誤したときの備忘録です。

で、なんとかできましたよ、と。

うまくいったコードを記事の下の方に載せていますので、ご興味ありましたらライセンス遵守下でご自由にお使いください。

関連記事:Kivyで作ったAndroidアプリにAdMobを入れたので、Google UMPを使ってGDPRに対応する、の巻

ちょっと経緯

これまで、firebase-ads 19.4.0ならばKivMobが動くっていうことで使っていたのですが、

もういよいよダメっぽいので、、、

Mobile Ads SDK (Android) サポートの終了と廃止

KivMobでの使用が謳われているfirebase-ads 21.4.0に上げることにしました。

すると、

AttributeError: 'AndroidBridge' object has no attribute '_interstitial’

などど言われて、これまで出ていたはずの広告が表示されないようになっているのでした。

バナー広告は表示されるのですが、インタースティシャル広告と動画リワード広告の両方ともダメ(これら以外は使ってないので未確認です)。

どうやらGoogleのAdMob広告関連のAPIに変更があったみたいでその影響のようでした。

ちなみに「動画リワード広告(RewardedVideoAd)」だったものはどうやら「リワード広告(RewardedAd)」ってのに変わってました。

ええ、それはもう随分と前に(2021年から2022年にかけてっぽい)。。。おおこわ。

インタースティシャル広告

リワード広告

どうやら跡形もなくなるくらい仕様変更されている模様。。。

firebase-adsの19.4.0と21.4.0ではギャップがすごいのね。だから動かないのね。

まあいいや、どうせPythonのクラス名とかをちょちょいと修正するくらいで何とかなるんじゃないの?

と、軽く考えていましたら、よく見たら必要なAPIが抽象クラスになってやがんの。

これまで(firebase-ads 19.4.0)はAdMobの広告表示に必要なAPIが具象クラスとインターフェースだけでできていたから(多分)、Pyjniusを使ってほぼそのままPython側に引っ張ってくることができていたのですが、抽象クラスの場合はそれができないようで、、、

https://github.com/Android-for-Python/Android-for-Python-Users#java-abstract-classes

すんなりPythonに持って来れないのですと。

なのでどうやらJavaをいじらないといけないらしい。カレーしか知らないですよワタシ。。。

あと、コールバック関数はPythonにどうやって落とし込むのよこれ、全くわからんぞ。

という状況でしたが、あれやこれや調べて試行錯誤してなんとかできました。ふぅ。

すみません長くなりました。本題に入ります。(間違いあったらごめんなさい)

実施環境

Buildozer 1.5.1.dev0

Python 3.9.9 (Buildozerが連れてくるもの)

Kivy 2.1.0 (Buildozerが連れてくるもの)

python-for-android v2023.02.10(Buildozerが連れてくるもの)

KivMob ver 2.0

MacBook Pro 2021, Apple M1 Pro

macOS Ventura version 13.3.1

方法

概要

だいたいこんな感じにKivMob周辺を改変しました。

コードについての詳細はコードのコメント行をご参照ください。

  • Javaのコードで該当する抽象クラスを継承させた具象クラスを作って、そこからPyjniusでPythonに引っ張ってこれるようにした
    • InterstitialAdLoadCallbackを継承したInterstitialAdLoadCallback4kivyクラスを作って、各メソッドをオーバーライドしたものをInterstitialAdLoadCallback4kivy.javaとして作成
    • RewardedAdLoadCallbackを継承したRewardedAdLoadCallback4kivyクラスを作って、各メソッドをオーバーライドしたものをRewardedAdLoadCallback4kivy.javaとして作成
    • javaファイルの1行目のpackageは、buildozer.specのpackage.domainとpackage.nameを組み合わせたもの
    • buildozerを実行するディレクトリの中にsrcディレクトリを作成して、作成したjavaファイルを格納(.specファイルで指定する)
  • KivMobのkivmob.pyをローカルにコピーしてきて、今のMobile Ads SDK (Android)のAPIの仕様に沿うように追記修正
    なお、追記修正したファイル名をkivmob_mod.pyとしました。
    • Android API(Java)とJavaのクラスのいくつかをPyjniusのautoclassでインポート
    • OnUserEarnedRewardListenerインターフェースをPyjniusを使って実装
    • 対応する広告読み込み/表示用関数などの作成、改変、削除
  • main.pyを該当APIの仕様に沿うように作成
    • kivmob_modのRewardedListenerInterfaceクラスを継承したRewardHandlerクラスを作成して、RewardedAdにおけるリワード発生時の処理を記載
    • 対応する広告読み込み/表示用関数などの作成
  • main.pyに設置した広告関連の関数を発動する仕組みの作成
    • ここではmain.kvファイルにて、広告のロードや表示をする関数を発動させるボタンを定義しました。
  • buildozer.specの追記
    • .javaファイルを格納したsrcディレクトリの指定
    • android.gradle_dependencies = com.google.firebase:firebase-ads:21.4.0, androidx.appcompat:appcompat:1.6.1, androidx.activity:activity:1.6.1
    • 他、KivMob公式を参考にして記載

コード

コードの中のコメント行に説明などを記載しています(過不足あるかと思いますが)。

main.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.utils import platform
from kivy.logger import Logger

if platform == 'android':
    from kivmob_mod import KivMob, TestIds, RewardedListenerInterface

#give class the name of Otameshi 
class Otameshi(BoxLayout):
    pass

#Kivmob rewarded ads
class RewardsHandler(RewardedListenerInterface):
    def on_rewarded(self, reward_type, reward_amount):
        print("User rewarded", "Type; ", reward_type, "Amount; ", reward_amount)

        #load rewarded_ads
        App.get_running_app().ads.load_rewarded_ad(TestIds.REWARDED_VIDEO) #テスト用


#give class the name of MainApp 
class MainApp(App):

    def __init__(self, **kwargs):
        super(MainApp, self).__init__(**kwargs)
        self.title = "kivmob_test"

    def build(self):
        if platform == 'android':
            self.ads = KivMob(TestIds.APP) #テスト用
            
            #banner
            self.ads.new_banner(TestIds.BANNER,top_pos=False) #テスト用
            self.ads.request_banner()
            self.ads.show_banner()

            #interstitial
            self.ads.load_interstitial(TestIds.INTERSTITIAL) #テスト用

            #rewarded_ad
            self.ads.load_rewarded_ad(TestIds.REWARDED_VIDEO) #テスト用
            #RewardedAdLoadCallback4kivyを継承したRewardsHandlerのインスタンスをset_rewarded_ad_listenerに渡す
            self.ads.set_rewarded_ad_listener(RewardsHandler())

        return Otameshi()

    def on_resume(self):
        Logger.info("kivmob_test: on_resume()")
        if platform == 'android':
            self.load_ads()

    def load_ads(self):
        if platform == 'android':
            Logger.info("kivmob_test: load_ads() fired")

            #banner
            self.ads.request_banner()

            #interstitial
            self.ads.load_interstitial(TestIds.INTERSTITIAL) #テスト用

            #rewarded_ad
            self.ads.load_rewarded_ad(TestIds.REWARDED_VIDEO) #テスト用

    def show_banner(self):
        if platform == 'android':
            Logger.info("kivmob_test: show_banner() fired")
            self.ads.show_banner()

    def hide_banner(self):
        if platform == 'android':
            Logger.info("kivmob_test: hide_banner() fired")
            self.ads.hide_banner()

    def load_interstitial(self):
        if platform == 'android':
            Logger.info("kivmob_test: load_interstitial() fired")
            self.ads.load_interstitial(TestIds.INTERSTITIAL) #テスト用

    def show_interstitial(self):
        if platform == 'android':
            Logger.info("kivmob_test: show_interstitial() fired")
            self.ads.show_interstitial()

    def load_rewarded_ad(self):
        if platform == 'android':
            Logger.info("kivmob_test: load_rewarded_ad() fired")
            self.ads.load_rewarded_ad(TestIds.REWARDED_VIDEO) #テスト用

    def show_rewarded_ad(self):
        if platform == 'android':
            Logger.info("kivmob_test: show_rewarded_ad() fired")
            self.ads.show_rewarded_ad()

if __name__ == '__main__':
    MainApp().run()

main.kv

<Otameshi>
    BoxLayout:
        orientation:'vertical'
        size:root.size
        padding:(100,100,100,300) # padding:(left, top, right, bottom)
        Label:
            id: lab1
            text: self.text
            font_size: 80
        Button:
            size: 100, 200
            text: 'hide banner'
            pos: 0,0
            font_size: 80
            on_release:
                app.hide_banner()
        Button:
            size: 100, 200
            text: 'show banner'
            pos: 0,0
            font_size: 80
            on_release:
                app.show_banner()
        Button:
            size: 100, 200
            text: 'show interstitial'
            pos: 0,0
            font_size: 80
            on_release:
                app.show_interstitial()
        Button:
            size: 100, 200
            text: 'show rewarded video'
            pos: 0,0
            font_size: 80
            on_release:
                app.show_rewarded_ad()
        Button:
            size: 100, 200
            text: 'load ads'
            pos: 0,0
            font_size: 80
            on_press:
                root.ids['lab1'].text='osunatte'
            on_release:
                root.ids['lab1'].text='';
                app.load_ads()

kivmob_mod.py

from kivy.core.window import Window
from kivy.logger import Logger
from kivy.metrics import dp
from kivy.utils import platform


if platform == "android":
    try:
        from jnius import autoclass, cast, PythonJavaClass, java_method
        from android.runnable import run_on_ui_thread

        activity = autoclass("org.kivy.android.PythonActivity")

        #no longer needed
        #AdListener = autoclass("com.google.android.gms.ads.AdListener")

        AdMobAdapter = autoclass("com.google.ads.mediation.admob.AdMobAdapter")
        AdRequest = autoclass("com.google.android.gms.ads.AdRequest")
        AdRequestBuilder = autoclass("com.google.android.gms.ads.AdRequest$Builder")
        AdSize = autoclass("com.google.android.gms.ads.AdSize")
        AdView = autoclass("com.google.android.gms.ads.AdView")
        Bundle = autoclass("android.os.Bundle")
        Gravity = autoclass("android.view.Gravity")
        InterstitialAd = autoclass("com.google.android.gms.ads.interstitial.InterstitialAd")
        LayoutParams = autoclass("android.view.ViewGroup$LayoutParams")
        LinearLayout = autoclass("android.widget.LinearLayout")
        MobileAds = autoclass("com.google.android.gms.ads.MobileAds")
        RewardItem = autoclass("com.google.android.gms.ads.rewarded.RewardItem")
        #RewardedVideoAd = autoclass("com.google.android.gms.ads.rewarded.RewardedVideoAd")
        #RewardedVideoAdListener = autoclass("com.google.android.gms.ads.rewarded.RewardedVideoAdListener")
        View = autoclass("android.view.View")

        #for modify kivmob
        appCompatActivity = cast("androidx.appcompat.app.AppCompatActivity", activity.mActivity)
        Context = autoclass('android.content.Context')
        LoadAdError = autoclass('com.google.android.gms.ads.LoadAdError')
        RewardedAdLoadCallback4kivy = autoclass('org.test.kivmob_test.RewardedAdLoadCallback4kivy')

        #for making toast
        AndroidString = autoclass('java.lang.String')
        Toast = autoclass('android.widget.Toast')

        # set a listener for rewarded ad
        class OnUserEarnedRewardListener(PythonJavaClass):
            __javacontext__ = 'app'
            __javainterfaces__ = ["com.google.android.gms.ads.OnUserEarnedRewardListener"]

            def __init__(self, listener):
                super().__init__()
                self._listener = listener

                #for making toast
                self.context = activity.mActivity.getApplicationContext()

            # ユーザーが報酬を獲得した時の処理
            @java_method('(Lcom/google/android/gms/ads/rewarded/RewardItem;)V')
            def onUserEarnedReward(self, reward_item):
                Logger.info("KivMob: onUserEarnedReward fired in kivmob_mod.py")

                #toast
                duration = Toast.LENGTH_LONG
                text = f"You got reward! Type:{reward_item.getType()}, Amount:{reward_item.getAmount()}"
                #text = 'user got reward. type, amount'
                text_char_sequence = cast('java.lang.CharSequence', AndroidString(text))
                toast = Toast.makeText(self.context, text_char_sequence, duration)
                toast.show()

                # main.py内のRewardsHandlerのon_rewardedを発動
                self._listener.on_rewarded(
                    reward_item.getType(),
                    reward_item.getAmount()
                )

        """ TODO since no more RewardedVideoAd
        class AdMobRewardedVideoAdListener(PythonJavaClass):
            __javainterfaces__ = (
                "com.google.android.gms.ads.reward.RewardedVideoAdListener",
            )
            __javacontext__ = "app"
            def __init__(self, listener):
                self._listener = listener
            @java_method("(Lcom/google/android/gms/ads/reward/RewardItem;)V")
            def onRewarded(self, reward):
                Logger.info("KivMob: onRewarded() called.")
                self._listener.on_rewarded(
                    reward.getType(), reward.getAmount()
                )
            @java_method("()V")
            def onRewardedVideoAdLeftApplication(self):
                Logger.info(
                    "KivMob: onRewardedVideoAdLeftApplicaxtion() called."
                )
                self._listener.on_rewarded_video_ad_left_application()
            @java_method("()V")
            def onRewardedVideoAdClosed(self):
                Logger.info("KivMob: onRewardedVideoAdClosed() called.")
                self._listener.on_rewarded_video_ad_closed()
            @java_method("(I)V")
            def onRewardedVideoAdFailedToLoad(self, errorCode):
                Logger.info("KivMob: onRewardedVideoAdFailedToLoad() called.")
                # Logger.info("KivMob: ErrorCode " + str(errorCode))
                self._listener.on_rewarded_video_ad_failed_to_load(errorCode)
            @java_method("()V")
            def onRewardedVideoAdLoaded(self):
                Logger.info("KivMob: onRewardedVideoAdLoaded() called.")
                self._listener.on_rewarded_video_ad_loaded()
            @java_method("()V")
            def onRewardedVideoAdOpened(self):
                Logger.info("KivMob: onRewardedVideoAdOpened() called.")
                self._listener.on_rewarded_video_ad_opened()
            @java_method("()V")
            def onRewardedVideoStarted(self):
                Logger.info("KivMob: onRewardedVideoStarted() called.")
                self._listener.on_rewarded_video_ad_started()
            @java_method("()V")
            def onRewardedVideoCompleted(self):
                Logger.info("KivMob: onRewardedVideoCompleted() called.")
                self._listener.on_rewarded_video_ad_completed()
        """

    except BaseException:
        Logger.error(
            "KivMob: Cannot load AdMob classes. Check buildozer.spec."
        )
else:

    """
    class AdMobRewardedVideoAdListener:
        pass
    """

    def run_on_ui_thread(x):
        pass


class TestIds:
    """ Enum of test ad ids provided by AdMob. This allows developers to
        test displaying ads without setting up an AdMob account.
    """

    """ Test AdMob App ID """
    APP = "ca-app-pub-3940256099942544~3347511713"

    """ Test Banner Ad ID """
    BANNER = "ca-app-pub-3940256099942544/6300978111"

    """ Test Interstitial Ad ID """
    INTERSTITIAL = "ca-app-pub-3940256099942544/1033173712"

    """ Test Interstitial Video Ad ID """
    INTERSTITIAL_VIDEO = "ca-app-pub-3940256099942544/8691691433"

    """ Test Rewarded Video Ad ID """
    REWARDED_VIDEO = "ca-app-pub-3940256099942544/5224354917"


class AdMobBridge:
    def __init__(self, appID):
        pass

    def add_test_device(self, testID):
        pass

    def is_interstitial_loaded(self):
        return False

    def new_banner(self, unitID, top_pos=True):
        pass

    #set load_interstitial function instead of new/request_interstitial function
    def load_interstitial(self, unitID):
        pass

    #def new_interstitial(self, unitID):
    #    pass

    def request_banner(self, options):
        pass

    #def request_interstitial(self, options):
    #    pass

    def show_banner(self):
        pass

    def show_interstitial(self):
        pass

    def destroy_banner(self):
        pass

    #original. no longer needed
    #def destroy_interstitial(self):
    #    pass

    def hide_banner(self):
        pass

    def set_rewarded_ad_listener(self, listener):
        pass

    def load_rewarded_ad(self, unitID):
        pass

    def show_rewarded_ad(self):
        pass


class RewardedListenerInterface:
    """ Interface for objects that handle rewarded video ad
        callback functions
    """

    #just changed "name" to "type"
    def on_rewarded(self, reward_type, reward_amount):
        """ Called when the video completes

            :type reward_type: string
            :param reward_type: Type of the reward.
            :type reward_amount: string
            :param reward_amount: Amount of the reward.
        """
        pass

    #original
    #def on_rewarded(self, reward_name, reward_amount):
    #    """ Called when the video completes
    #
    #        :type reward_name: string
    #        :param reward_name: Name of the reward.
    #        :type reward_amount: string
    #        :param reward_amount: Amount of the reward.
    #    """
    #    pass
    #
    #def on_rewarded_video_ad_left_application(self):
    #    """ Called when the user closes the application while
    #        the video is playing.
    #    """
    #    pass
    #
    #def on_rewarded_video_ad_closed(self):
    #    """ Called when the user manually closes the ad before completion.
    #    """
    #    pass
    #
    #def on_rewarded_video_ad_failed_to_load(self, error_code):
    #    """ Called when the rewarded video ad fails to load.
    #
    #        :type error_code: int
    #        :param error_code: Integer code that corresponds to the error.
    #    """
    #    pass
    #
    #def on_rewarded_video_ad_loaded(self):
    #    """ Called when the rewarded ad finishes loading.
    #    """
    #    pass
    #
    #def on_rewarded_video_ad_opened(self):
    #    """ Called when the rewarded ad is opened.
    #    """
    #    pass
    #
    #def on_rewarded_video_ad_started(self):
    #    """ Called when the rewarded video ad starts.
    #    """
    #    pass
    #
    #def on_rewarded_video_ad_completed(self):
    #    """ Called when the rewarded video ad completes.
    #    """
    #    pass


class AndroidBridge(AdMobBridge):
    @run_on_ui_thread
    def __init__(self, appID):
        super().__init__(appID)
        self._loaded = False
        try:
            MobileAds.initialize(activity.mActivity)
        except Exception as error:
            print('KivMob error@AndroidBridge __init__', error)

        self._test_devices = []

        # AdMob's instantiation just for banner ads
        self._adview = AdView(activity.mActivity)

        # AdMob Interstitial ad call back
        InterstitialAdLoadCallback4kivy = autoclass('org.test.kivmob_test.InterstitialAdLoadCallback4kivy')
        self.interstitialAdLoadCallback4kivy = InterstitialAdLoadCallback4kivy()

        # toast用
        self.context = activity.mActivity.getApplicationContext()

        """ original 
        self._loaded = False

        try:
            MobileAds.initialize(activity.mActivity)

        except ValueError as error:
            print(error)
            
        self._adview = AdView(activity.mActivity)
        self._interstitial = InterstitialAd(activity.mActivity)
        self._rewarded = MobileAds.getRewardedVideoAdInstance(
            activity.mActivity
        )
        self._test_devices = []
        """

    @run_on_ui_thread
    def add_test_device(self, testID):
        self._test_devices.append(testID)

    @run_on_ui_thread
    def new_banner(self, unitID, top_pos=True):
        self._adview = AdView(activity.mActivity)
        self._adview.setAdUnitId(unitID)
        self._adview.setAdSize(AdSize.SMART_BANNER)
        self._adview.setVisibility(View.GONE)
        adLayoutParams = LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT
        )
        self._adview.setLayoutParams(adLayoutParams)
        layout = LinearLayout(activity.mActivity)
        if not top_pos:
            layout.setGravity(Gravity.BOTTOM)
        layout.addView(self._adview)
        layoutParams = LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT
        )
        layout.setLayoutParams(layoutParams)
        activity.mActivity.addContentView(layout, layoutParams)

    @run_on_ui_thread
    def request_banner(self, options={}):
        self._adview.loadAd(self._get_builder(options).build())

    @run_on_ui_thread
    def show_banner(self):
        self._adview.setVisibility(View.VISIBLE)

    @run_on_ui_thread
    def hide_banner(self):
        self._adview.setVisibility(View.GONE)

    @run_on_ui_thread
    def load_interstitial(self, unitID, options={}):
        adRequest = self._get_builder(options).build()

        InterstitialAd.load(
            appCompatActivity,
            unitID,
            adRequest,
            self.interstitialAdLoadCallback4kivy)

    #original. no longer needed
    #@run_on_ui_thread
    #def new_interstitial(self, unitID):
    #    self._interstitial.setAdUnitId(unitID)
    #
    #@run_on_ui_thread
    #def request_interstitial(self, options={}):
    #    self._interstitial.loadAd(self._get_builder(options).build())
    #
    #@run_on_ui_thread
    #def _is_interstitial_loaded(self):
    #    self._loaded = self._interstitial.isLoaded()
    #
    #def is_interstitial_loaded(self):
    #    self._is_interstitial_loaded()
    #    return self._loaded

    @run_on_ui_thread
    def show_interstitial(self):
        if self.interstitialAdLoadCallback4kivy.mInterstitialAd is not None:
            self.interstitialAdLoadCallback4kivy.mInterstitialAd.show(activity.mActivity)
        else:
            from jnius import autoclass, cast
            AndroidString = autoclass('java.lang.String')
            Toast = autoclass('android.widget.Toast')
            duration = Toast.LENGTH_LONG
            text = 'Ads not loaded yet... Please try again after pressing the "load Ads" button.'
            text_char_sequence = cast('java.lang.CharSequence', AndroidString(text))
            toast = Toast.makeText(self.context, text_char_sequence, duration)
            toast.show()

        #original 
        #if self.is_interstitial_loaded():
        #    self._interstitial.show()

    @run_on_ui_thread
    def set_rewarded_ad_listener(self, listener):
        self._listener = OnUserEarnedRewardListener(listener)
        return listener

    @run_on_ui_thread
    def load_rewarded_ad(self, unitID, options={}):
        RewardedAd = autoclass("com.google.android.gms.ads.rewarded.RewardedAd")
        RewardedAdLoadCallback4kivy = autoclass('org.test.kivmob_test.RewardedAdLoadCallback4kivy')
        self.rewardedAdLoadCallback4kivy = RewardedAdLoadCallback4kivy()
        adRequest = self._get_builder(options).build()

        RewardedAd.load(
            appCompatActivity,
            unitID,
            adRequest,
            self.rewardedAdLoadCallback4kivy)

    @run_on_ui_thread
    def show_rewarded_ad(self):
        if self.rewardedAdLoadCallback4kivy.mRewardedAd != None:
            self.rewardedAdLoadCallback4kivy.mRewardedAd.show(activity.mActivity, self._listener)
        else:
            from jnius import autoclass, cast
            AndroidString = autoclass('java.lang.String')
            Toast = autoclass('android.widget.Toast')
            duration = Toast.LENGTH_LONG
            text = 'Ads not loaded yet... Please try again after pressing the "load Ads" button.'
            text_char_sequence = cast('java.lang.CharSequence', AndroidString(text))
            toast = Toast.makeText(self.context, text_char_sequence, duration)
            toast.show()

    #original
    #@run_on_ui_thread
    #def set_rewarded_ad_listener(self, listener):
    #    self._listener = AdMobRewardedVideoAdListener(listener)
    #    self._rewarded.setRewardedVideoAdListener(self._listener)
    #
    #@run_on_ui_thread
    #def load_rewarded_ad(self, unitID):
    #    builder = self._get_builder(None)
    #    self._rewarded.loadAd(unitID, builder.build())
    #
    #@run_on_ui_thread
    #def show_rewarded_ad(self):
    #    if self._rewarded.isLoaded():
    #        self._rewarded.show()

    @run_on_ui_thread
    def destroy_banner(self):
        self._adview.destroy()

    #original. no longer needed
    #@run_on_ui_thread
    #def destroy_interstitial(self):
    #    self._interstitial.destroy()
    #
    #@run_on_ui_thread
    #def destroy_rewarded_video_ad(self):
    #    self._rewarded.destroy()

    def _get_builder(self, options):
        builder = AdRequestBuilder()
        if options is not None:
            if "children" in options:
                builder.tagForChildDirectedTreatment(options["children"])
            if "family" in options:
                extras = Bundle()
                extras.putBoolean(
                    "is_designed_for_families", options["family"]
                )
                builder.addNetworkExtrasBundle(AdMobAdapter, extras)
        for test_device in self._test_devices:
            if len(self._test_devices) != 0:
                builder.addTestDevice(test_device)
                
        return builder


class iOSBridge(AdMobBridge):
    # TODO
    pass


class KivMob:
    """ Allows access to AdMob functionality on Android devices.
    """

    def __init__(self, appID):
        Logger.info("KivMob: __init__ called.")
        self._banner_top_pos = True
        if platform == "android":
            Logger.info("KivMob: Android platform detected.")
            self.bridge = AndroidBridge(appID)
        elif platform == "ios":
            Logger.warning("KivMob: iOS not yet supported.")
            self.bridge = iOSBridge(appID)
        else:
            Logger.warning("KivMob: Ads will not be shown.")
            self.bridge = AdMobBridge(appID)

    def add_test_device(self, device):
        """ Add test device ID, which will trigger test ads to be displayed on
            that device

            :type device: string
            :param device: The test device ID of the Android device.
        """
        Logger.info("KivMob: add_test_device() called.")
        self.bridge.add_test_device(device)

    def new_banner(self, unitID, top_pos=True):
        """ Create a new mobile banner ad.

            :type unitID: string
            :param unitID: AdMob banner ID for mobile application.
            :type top_pos: boolean
            :param top_pos: Positions banner at the top of the page if True,
            bottom if otherwise.
        """
        Logger.info("KivMob: new_banner() called.")
        self.bridge.new_banner(unitID, top_pos)

    def load_interstitial(self, unitID):
        """ load a new mobile interstitial ad.

            :type unitID: string
            :param unitID: AdMob interstitial ID for mobile application.
        """
        Logger.info("KivMob: load_interstitial() called.")
        self.bridge.load_interstitial(unitID)

    #original. no longer needed
    #def new_interstitial(self, unitID):
    #    """ Create a new mobile interstitial ad.
    #
    #        :type unitID: string
    #        :param unitID: AdMob interstitial ID for mobile application.
    #    """
    #    Logger.info("KivMob: new_interstitial() called.")
    #    self.bridge.new_interstitial(unitID)
    #
    #def is_interstitial_loaded(self):
    #    """ Check if the interstitial ad has loaded.
    #    """
    #    Logger.info("KivMob: is_interstitial_loaded() called.")
    #    return self.bridge.is_interstitial_loaded()

    def request_banner(self, options={}):
        """ Request a new banner ad from AdMob.
        """
        Logger.info("KivMob: request_banner() called.")
        self.bridge.request_banner(options)

    #original. no longer needed
    #def request_interstitial(self, options={}):
    #    """ Request a new interstitial ad from AdMob.
    #    """
    #    Logger.info("KivMob: request_interstitial() called.")
    #    self.bridge.request_interstitial(options)

    def show_banner(self):
        """ Displays banner ad, if it has loaded.
        """
        Logger.info("KivMob: show_banner() called.")
        self.bridge.show_banner()

    def show_interstitial(self):
        """ Displays interstitial ad, if it has loaded.
        """
        Logger.info("KivMob: show_interstitial() called.")
        self.bridge.show_interstitial()

    def destroy_banner(self):
        """ Destroys current banner ad.
        """
        Logger.info("KivMob: destroy_banner() called.")
        self.bridge.destroy_banner()

    #original. no longer needed
    #def destroy_interstitial(self):
    #    """ Destroys current interstitial ad.
    #    """
    #    Logger.info("KivMob: destroy_interstitial() called.")
    #    self.bridge.destroy_interstitial()

    def hide_banner(self):
        """  Hide current banner ad.
        """
        Logger.info("KivMob: hide_banner() called.")
        self.bridge.hide_banner()

    #just change listener to RewardedAdLoadCallback4kivy
    def set_rewarded_ad_listener(self, listener):
        """ Set listener object for rewarded video ads.

            :type listener: RewardedAdLoadCallback4kivy
            :param listener: Handles callback functionality for
            rewarded video ads.
        """
        Logger.info("KivMob: set_rewarded_ad_listener() called.")
        self.bridge.set_rewarded_ad_listener(listener)

    #original
    #def set_rewarded_ad_listener(self, listener):
    #    """ Set listener object for rewarded video ads.

    #        :type listener: AdMobRewardedVideoAdListener
    #        :param listener: Handles callback functionality for
    #        rewarded video ads.
    #    """
    #    Logger.info("KivMob: set_rewarded_ad_listener() called.")
    #    self.bridge.set_rewarded_ad_listener(listener)

    def load_rewarded_ad(self, unitID):
        """ Load rewarded video ad.

            :type unitID: string
            :param unitID: AdMob rewarded video ID for mobile application.
        """
        Logger.info("KivMob: load_rewarded_ad() called.")
        self.bridge.load_rewarded_ad(unitID)

    def show_rewarded_ad(self):
        """ Display rewarded video ad.
        """
        Logger.info("KivMob: show_rewarded_ad() called.")
        self.bridge.show_rewarded_ad()

    def determine_banner_height(self):
        """ Utility function for determining the height (dp) of the banner ad.

            :return height: Height of banner ad in dp.
        """
        height = dp(32)
        upper_bound = dp(720)
        if Window.height > upper_bound:
            height = dp(90)
        elif dp(400) < Window.height <= upper_bound:
            height = dp(50)
        return height


if __name__ == "__main__":
    print(
        "\033[92m  _  ___       __  __       _\n"
        " | |/ (_)_   _|  \\/  | ___ | |__\n"
        " | ' /| \\ \\ / / |\\/| |/ _ \\| '_ \\\n"
        " | . \\| |\\ V /| |  | | (_) | |_) |\n"
        " |_|\\_\\_| \\_/ |_|  |_|\\___/|_.__/\n\033[0m"
    )
    print(" AdMob support for Kivy\n")
    print(" Michael Stott, 2019\n")

InterstitialAdLoadCallback4kivy.java

package org.test.kivmob_test;

import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.gms.ads.interstitial.InterstitialAd;
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback;
import com.google.android.gms.ads.FullScreenContentCallback;
import com.google.android.gms.ads.AdError;



public class InterstitialAdLoadCallback4kivy extends InterstitialAdLoadCallback {

    private static final String TAG = "InterstitialAdLoadCallback4kivy";
    private InterstitialAd mInterstitialAd;

    @Override
    public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {
        // What to do if an ad loads successfully is here :
        mInterstitialAd = interstitialAd;
        Log.d(TAG, "Interstitial Ad loaded.");

        // Process for a loaded ad is here
        mInterstitialAd.setFullScreenContentCallback(new FullScreenContentCallback(){
            @Override
            public void onAdClicked() {
              // Called when a click is recorded for an ad.
              Log.d(TAG, "Interstitial Ad was clicked.");
            }
          
            @Override
            public void onAdDismissedFullScreenContent() {
              // Called when ad is dismissed.
              // Set the ad reference to null so you don't show the ad a second time.
              Log.d(TAG, "Interstitial Ad dismissed fullscreen content.");
              mInterstitialAd = null;
            }
          
            @Override
            public void onAdFailedToShowFullScreenContent(AdError adError) {
              // Called when ad fails to show.
              Log.e(TAG, "Interstitial Ad failed to show fullscreen content.");
              mInterstitialAd = null;
            }
          
            @Override
            public void onAdImpression() {
              // Called when an impression is recorded for an ad.
              Log.d(TAG, "Interstitial Ad recorded an impression.");
            }
          
            @Override
            public void onAdShowedFullScreenContent() {
              // Called when ad is shown.
              Log.d(TAG, "Interstitial Ad showed fullscreen content.");
            }
          });
    }

    @Override
    public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
        // What to do if the ad fails to load is here
        Log.d(TAG, "Failed to load Interstitial ad: " + loadAdError.getMessage());
        // Error handling and alternative processing in case of loading failure are here
        mInterstitialAd = null;
    }
}

RewardedAdLoadCallback4kivy.java

package org.test.kivmob_test;

import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.gms.ads.rewarded.RewardedAd;
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback;
import com.google.android.gms.ads.FullScreenContentCallback;
import com.google.android.gms.ads.AdError;

public class RewardedAdLoadCallback4kivy extends RewardedAdLoadCallback {
    
    private static final String TAG = "RewardedAdLoadCallback4kivy";
    private RewardedAd mRewardedAd;

    @Override
    public void onAdLoaded(@NonNull RewardedAd rewardedAd) {
        // What to do if an ad loads successfully is here 
        // ie. perform ad display and playback
        mRewardedAd = rewardedAd;
        Log.d(TAG, "Rewarded Ad was loaded.");
        mRewardedAd.setFullScreenContentCallback(new FullScreenContentCallback() {
            @Override
            public void onAdClicked() {
              // Called when a click is recorded for an ad.
              Log.d(TAG, "Rewarded Ad was clicked.");
            }
          
             @Override
            public void onAdDismissedFullScreenContent() {
              // Called when ad is dismissed.
              // Set the ad reference to null so you don't show the ad a second time.
              Log.d(TAG, "Rewarded Ad dismissed fullscreen content.");
              mRewardedAd = null;
            }
          
            @Override
            public void onAdFailedToShowFullScreenContent(AdError adError) {
              // Called when ad fails to show.
              Log.e(TAG, "Rewarded Ad failed to show fullscreen content.");
              mRewardedAd = null;
            }
          
            @Override
            public void onAdImpression() {
              // Called when an impression is recorded for an ad.
              Log.d(TAG, "Rewarded Ad recorded an impression.");
            }
          
            @Override
            public void onAdShowedFullScreenContent() {
              // Called when ad is shown.
              Log.d(TAG, "Rewarded Ad showed fullscreen content.");
            }
          });
    }
    
    @Override
    public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
        // What to do if the ad fails to load is here
        // ie. obtain the cause of the error and detailed information
        Log.d(TAG, loadAdError.toString());
        mRewardedAd = null;
    }
}

buidozer.spec

[app]

# (str) Title of your application
title = kivmob_test

# (str) Package name
package.name = kivmob_test

# (str) Package domain (needed for android/ios packaging)
package.domain = org.test

# (str) Source code where the main.py live
source.dir = .

# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas

# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png

# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec

# (list) List of directory to exclude (let empty to not exclude anything)
source.exclude_dirs = tests, bin, venv, .gitignore, .DS_Store, .idea, .git

# (list) List of exclusions using pattern matching
# Do not prefix with './'
#source.exclude_patterns = license,images/*/*.jpg

# (str) Application versioning (method 1)
version = 0.1

# (str) Application versioning (method 2)
# version.regex = __version__ = ['"](.*)['"]
# version.filename = %(source.dir)s/main.py

# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy,android,jnius 


# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy

# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png

# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png

# (list) Supported orientations
# Valid options are: landscape, portrait, portrait-reverse or landscape-reverse
orientation = portrait

# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY

#
# OSX Specific
#

#
# author = © Copyright Info

# change the major version of python used by the app
osx.python_version = 3

# Kivy version to use
osx.kivy_version = 1.9.1

#
# Android specific
#

# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0

# (string) Presplash background color (for android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF

# (string) Presplash animation using Lottie format.
# see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/
# for general documentation.
# Lottie files can be created using various tools, like Adobe After Effect or Synfig.
#android.presplash_lottie = "path/to/lottie/file.json"

# (str) Adaptive icon of the application (used if Android API level is 26+ at runtime)
#icon.adaptive_foreground.filename = %(source.dir)s/data/icon_fg.png
#icon.adaptive_background.filename = %(source.dir)s/data/icon_bg.png

# (list) Permissions
# (See https://python-for-android.readthedocs.io/en/latest/buildoptions/#build-options-1 for all the supported syntaxes and properties)
#android.permissions = android.permission.INTERNET, (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)
android.permissions = INTERNET, ACCESS_NETWORK_STATE

# (list) features (adds uses-feature -tags to manifest)
#android.features = android.hardware.usb.host

# (int) Target Android API, should be as high as possible.
android.api = 33

# (int) Minimum API your APK / AAB will support.
android.minapi = 21

# (int) Android SDK version to use
android.sdk = 33

# (str) Android NDK version to use
android.ndk = 25b

# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21

# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =

# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =

# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =

# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False

# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False

# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.kivy.android.PythonActivity

# (str) Full name including package path of the Java class that implements Android Activity
# use that parameter together with android.entrypoint to set custom Java class instead of PythonActivity
#android.activity_class_name = org.kivy.android.PythonActivity

# (str) Extra xml to write directly inside the <manifest> element of AndroidManifest.xml
# use that parameter to provide a filename from where to load your custom XML code
#android.extra_manifest_xml = ./src/android/extra_manifest.xml

# (str) Extra xml to write directly inside the <manifest><application> tag of AndroidManifest.xml
# use that parameter to provide a filename from where to load your custom XML arguments:
#android.extra_manifest_application_arguments = ./src/android/extra_manifest_application_arguments.xml

# (str) Full name including package path of the Java class that implements Python Service
# use that parameter to set custom Java class which extends PythonService
#android.service_class_name = org.kivy.android.PythonService

# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"

# (list) Pattern to whitelist for the whole project
#android.whitelist =

# (bool) If True, your application will be listed as a home app (launcher app)
# android.home_app = False

# (str) Path to a custom whitelist file
#android.whitelist_src =

# (str) Path to a custom blacklist file
#android.blacklist_src =

# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar

# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
android.add_src = ./src

# (list) Android AAR archives to add
#android.add_aars =

# (list) Put these files or directories in the apk assets directory.
# Either form may be used, and assets need not be in 'source.include_exts'.
# 1) android.add_assets = source_asset_relative_path
# 2) android.add_assets = source_asset_path:destination_asset_relative_path
#android.add_assets =

# (list) Put these files or directories in the apk res directory.
# The option may be used in three ways, the value may contain one or zero ':'
# Some examples:
# 1) A file to add to resources, legal resource names contain ['a-z','0-9','_']
# android.add_resources = my_icons/all-inclusive.png:drawable/all_inclusive.png
# 2) A directory, here  'legal_icons' must contain resources of one kind
# android.add_resources = legal_icons:drawable
# 3) A directory, here 'legal_resources' must contain one or more directories, 
# each of a resource kind:  drawable, xml, etc...
# android.add_resources = legal_resources
#android.add_resources =

# (list) Gradle dependencies to add
android.gradle_dependencies = com.google.firebase:firebase-ads:21.4.0, androidx.appcompat:appcompat:1.6.1, androidx.activity:activity:1.6.1 

# (bool) Enable AndroidX support. Enable when 'android.gradle_dependencies'
# contains an 'androidx' package, or any package from Kotlin source.
# android.enable_androidx requires android.api >= 28
android.enable_androidx = True

# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"

# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes 
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =

# (list) packaging options to add 
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes 
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_packaging_options =

# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity

# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME

# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png

# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =

# (list) Copy these files to src/main/res/xml/ (used for example with intent-filters)
#android.res_xml = PATH_TO_FILE,

# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard

# (str) screenOrientation to set for the main activity.
# Valid values can be found at https://developer.android.com/guide/topics/manifest/activity-element
#android.manifest.orientation = fullSensor

# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so

# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False

# (list) Android application meta-data to set (key=value format)
android.meta_data = com.google.android.gms.ads.APPLICATION_ID=ca-app-pub-3940256099942544~3347511713

# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =

# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =

# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D

# (bool) Android logcat only display log for activity's pid
#android.logcat_pid_only = False

# (str) Android additional adb arguments
#android.adb_args = -H host.docker.internal

# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1

# (list) The Android archs to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
# In past, was `android.arch` as we weren't supporting builds for multiple archs at the same time.
android.archs = arm64-v8a, armeabi-v7a

# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1

# (bool) enables Android auto backup feature (Android API >=23)
android.allow_backup = True

# (str) XML file for custom backup rules (see official auto backup documentation)
# android.backup_rules =

# (str) If you need to insert variables into your AndroidManifest.xml file,
# you can do so with the manifestPlaceholders property.
# This property takes a map of key-value pairs. (via a string)
# Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"]
# android.manifest_placeholders = [:]

# (bool) Skip byte compile for .py files
# android.no-byte-compile-python = False

# (str) The format used to package the app for release mode (aab or apk or aar).
# android.release_artifact = aab

# (str) The format used to package the app for debug mode (apk or aar).
# android.debug_artifact = apk

#
# Python for android (p4a) specific
#

# (str) python-for-android URL to use for checkout
#p4a.url =

# (str) python-for-android fork to use in case if p4a.url is not specified, defaults to upstream (kivy)
#p4a.fork = kivy

# (str) python-for-android branch to use, defaults to master
p4a.branch = master

# (str) python-for-android specific commit to use, defaults to HEAD, must be within p4a.branch
#p4a.commit = HEAD

# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =

# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =

# (str) Filename to the hook for p4a
#p4a.hook =

# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2

# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =

# Control passing the --use-setup-py vs --ignore-setup-py to p4a
# "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not
# Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py
# NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate
# setup.py if you're using Poetry, but you need to add "toml" to source.include_exts.
#p4a.setup_py = false

# (str) extra command line arguments to pass when invoking pythonforandroid.toolchain
#p4a.extra_args =



#
# iOS specific
#

# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master

# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.12.2

# (bool) Whether or not to sign the code
ios.codesign.allowed = false

# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"

# (str) The development team to use for signing the debug version
#ios.codesign.development_team.debug = <hexstring>

# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s

# (str) The development team to use for signing the release version
#ios.codesign.development_team.release = <hexstring>

# (str) URL pointing to .ipa file to be installed
# This option should be defined along with `display_image_url` and `full_size_image_url` options.
#ios.manifest.app_url =

# (str) URL pointing to an icon (57x57px) to be displayed during download
# This option should be defined along with `app_url` and `full_size_image_url` options.
#ios.manifest.display_image_url =

# (str) URL pointing to a large icon (512x512px) to be used by iTunes
# This option should be defined along with `app_url` and `display_image_url` options.
#ios.manifest.full_size_image_url =


[buildozer]

# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2

# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1

# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer

# (str) Path to build output (i.e. .apk, .aab, .ipa) storage
# bin_dir = ./bin

#    -----------------------------------------------------------------------------
#    List as sections
#
#    You can define all the "list" as [section:key].
#    Each line will be considered as a option to the list.
#    Let's take [app] / source.exclude_patterns.
#    Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
#    This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#


#    -----------------------------------------------------------------------------
#    Profiles
#
#    You can extend section / key with a profile
#    For example, you want to deploy a demo version of your application without
#    HD content. You could first change the title to add "(demo)" in the name
#    and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
#    Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug

結果

冒頭の動画をご覧ください。

おわりに

はい、Kivyで作ったAndroidアプリにKivMobでAdMobの広告を表示したときの試行錯誤、の巻でした。

これでしばらくはKivyのAndroidアプリでの広告表示を継続できそうです。

勉強不足でまだ理解が追いついていませんが、今後また同じようなケースが起こったときも何とかなりそうな感じがしています。

以上、もしかしたらどなたかの役に立つかもしれないので記事にしてみました。

おしまい。

ライセンス

kivmob_mod.py, main.py

Copyright (c) 2023 bu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

The major design pattern of this code was abstracted from KivMob, which is subject to the same license.
Here is the original copyright notice for KivMob:

Copyright ©︎ 2019 Michael Stott

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

main.kv

Copyright (c) 2023 bu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

InterstitialAdLoadCallback4kivy.java および RewardedAdLoadCallback4kivy.java

ApacheLicense2.0

Copyright 2023 bu

Licensed under the Apache License, Version 2.0 (the “License”);

you may not use this file except in compliance with the License.

You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

buildozer.spec

Copyright (c) 2010-2017 Kivy Team and other contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

参考にしたサイトなど

*本文中に記載しているものは割愛している場合があります。

KivMob

python-for-android

buildozer

Pyjnius Documentation

Abstruct Classes in Pyjnius (GoogleGroups)

SDKの移行

インタースティシャル広告

リワード広告

OnUserEarnedRewardListener

MobileAds


ちょっと広告です
https://business.xserver.ne.jp/

https://www.xdomain.ne.jp/

wpX Speed / wpX

★LOLIPOP★

.tokyo

MuuMuu Domain!