Compose Multiplatform で Admobのバナー広告を表示する

kyamada,KotlinComposeMultiplatformAdmob

概要

Compose Multiplatform で、Android/iOSの環境で Admob のバナー広告を表示する方法を解説します。

バナー広告を表示する手順は以下の通りです。

  1. commonMain 以下で、 AdFactoryインターフェイスを作成する。AdFactoryはバナー広告を表示する AdBanner() メソッドを持つ。
  2. androidMain と iosMain 以下で、AdFactory を継承した AdFactoryImpl を実装する。そして、Koinを使って commonMainから呼び出せるようにする。
  3. commonMain 以下で、Compose Multiplatformで作成した画面内で、AdFactory.AdBanner() を呼び出す。

使用するライブラリ

実装

commonMain

commonMain/../ad/AdFactory.kt
import androidx.compose.runtime.Composable
 
interface AdFactory {
    @Composable
    fun AdBanner()
}
commonMain/../ui/screens/MainScreen.kt
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
 
class MainScreen : Screen, KoinComponent {
    @Composable
    override fun Content() {
        val adFactory: AdFactory by inject()
        Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
            Spacer(Modifier.weight(1f))
            // バナー広告を表示
            adFactory.AdBanner()
        }
    }
}

androidMain

androidMain/../ad/AdFactoryImpl.kt
import android.content.Context
import android.content.res.Resources
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.AdView
 
class AdFactoryImpl : AdFactory {
    @Composable
    override fun AdBanner() {
        if (LocalInspectionMode.current) {
            // プレビュー時は表示しない
            return
        }
        val displayMetrics = Resources.getSystem().displayMetrics
        val width = ((displayMetrics.widthPixels) / displayMetrics.density).toInt()
        val adSize = getAdBannerSize(LocalContext.current, width)
        val heightDp = with(LocalDensity.current) {
            adSize.getHeightInPixels(LocalContext.current).toDp()
        }
 
        AndroidView(
            modifier = Modifier
                .fillMaxWidth()
                .height(heightDp),
            factory = { context ->
                val adView = AdView(context)
                adView.setAdSize(adSize)
                adView.adUnitId = Admob.getAdBannerUnitId()
                adView.loadAd(AdRequest.Builder().build())
                adView
            },
        )
    }
 
    private fun getAdBannerSize(context: Context, width: Int): AdSize {
        return if (Admob.IS_BANNER_HIDDEN) {
            AdSize(width, 0)
        } else {
            AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(context, width)
        }
    }
}
androidMain/../MainApplication.kt
class MainApplication : Application(), Application.ActivityLifecycleCallbacks, LifecycleObserver,
    KoinComponent {
 
    override fun onCreate() {
        super.onCreate()
        MobileAds.initialize(this) { }
 
        startKoin {
            androidContext(this@MainApplication)
            modules(appModule)
        }
    }
androidMain/../AppModule.kt
import org.koin.dsl.module
 
val appModule = module {
    single<AdFactory> { AdFactoryImpl() }
}

iosMain

iosMain/../ad/AdFactoryImpl.kt
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.interop.UIKitView
import androidx.compose.ui.unit.dp
import kotlinx.cinterop.ExperimentalForeignApi
 
class AdFactoryImpl(private val adBannerViewFactory: AdBannerViewFactory) : AdFactory {
    @OptIn(ExperimentalForeignApi::class)
    @Composable
    override fun AdBanner() {
        UIKitView(
            factory = {
                adBannerViewFactory.createAdBannerView()
            },
            modifier = Modifier.fillMaxWidth().height(adBannerViewFactory.getHeight().dp)
        )
    }
}
iosMain/../ad/AdBannerViewFactory.kt
import platform.UIKit.UIView
 
interface AdBannerViewFactory {
    fun createAdBannerView(): UIView
    fun getHeight(): Int
}
iosMain/../KoinHelper.kt
import org.koin.core.context.startKoin
import org.koin.dsl.module
 
fun initKoin(
    adBannerViewFactory: AdBannerViewFactory
) {
    startKoin {
        modules(module {
            single<AdFactory> { AdFactoryImpl(adBannerViewFactory) }
        })
    }
}

iosApp

iosApp/Ad/AdBannerViewFactoryImpl.swift
import Foundation
import GoogleMobileAds
import shared
 
class AdBannerViewFactoryImpl: AdBannerViewFactory {
 
    func createAdBannerView() -> UIView {
        let bannerView = GADBannerView(adSize: GADAdSizeBanner)
        bannerView.adUnitID = Admob.getBannerAdUnitId()
        bannerView.rootViewController = UIWindow.getWindow()?.rootViewController
        bannerView.adSize = getAdSize(width: UIScreen.screenWidth)
        bannerView.load(GADRequest())
        return bannerView
    }
 
    func getHeight() -> Int32 {
        return Int32(getAdSize(width: UIScreen.screenWidth).size.height)
    }
 
    private func getAdSize(width: CGFloat) -> GADAdSize {
        if Admob.IsBannerHidden {
            return GADAdSize()
        } else {
            return GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(width)
        }
    }
}
iosApp/Extension/UIWindow+.swift
import UIKit
 
extension UIWindow {
    static func getWindow() -> UIWindow? {
        let scenes = UIApplication.shared.connectedScenes
        let windowScene = scenes.first as? UIWindowScene
        return windowScene?.windows.first
    }
}
iosApp/Extension/UIScreen+.swift
import UIKit
 
extension UIScreen {
   static let screenWidth = UIScreen.main.bounds.size.width
   static let screenHeight = UIScreen.main.bounds.size.height
   static let screenSize = UIScreen.main.bounds.size
}
iosApp/iOSApp.swift
import SwiftUI
import GoogleMobileAds
import shared
 
class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
      GADMobileAds.sharedInstance().start(completionHandler: nil)
      return true
  }
}
 
 
@main
struct iOSApp: App {
    // register app delegate for Firebase setup
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
 
    init() {
        KoinHelperKt.doInitKoin(
            adBannerViewFactory: AdBannerViewFactoryImpl()
        )
    }
 
	var body: some Scene {
		WindowGroup {
			ContentView()
		}
	}
}

参考

© 品川アプリ.RSS