マルチモジュール時代のAdmob設定
広告を表示したいなと思いAdmobを調べていたんですが、せっかくならちゃんとDIしてマルチモジュールで利用できるようにしようと思いやってみました。この記事ではAdmobの設定方法などは省略します。 目標としてはappモジュール(分割済みなら各featureモジュール)からはAdmobを意識せず広告が読み込める(=広告ライブラリが変わっても影響がない)ことです。
環境
- Dagger 2.23.2
- firebase-ads 18.1.1
- googler-services 4.3.0
- appcomapt 1.0.2
- recyclerview 1.0.0
- Kotlin 1.3.41
- Android Studio 3.4.2
モジュール構成
今回は広告のインターフェースとして advertisement
モジュールとその実装として admob
モジュールを用意しました。feature_hogeモジュールからはadvertisementモジュールのみが見えており、DIでその実装であるadmobモジュールの中身が渡されるというフローです。appは全てを知っており、DIを解決します。
advertisementモジュール
advertisementモジュールはインターフェースです。そのためDaggerに依存しません。今回はRecyclerViewの中で使いたかったのでViewHolderを用意し、汎用的にFragmentもabstract classとして用意します。onBindViewHolder()内でAdViewHolder#bind()が呼ばれる想定です。
abstract class AdViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { abstract fun bind() } abstract class AdFragment : Fragment()
これらを動的に利用するために AdProvider
インターフェースを提供します。
interface AdProvider { fun init(context: Context) fun newViewHolderInstance(parent: ViewGroup): AdViewHolder fun newFragmentInstance(): AdFragment }
admobモジュール
admobモジュールはadvertisementモジュールで定義されたインターフェースを実装しDIで提供します。
class AdmobViewHolder( parent: ViewGroup, itemView: View = LayoutInflater.from(parent.context).inflate(R.layout.item_admob, parent, false) ) : AdViewHolder(itemView) { private val adView: AdView = itemView.findViewById(R.id.adView) override fun bind() { adView.loadAd(AdRequest.Builder().build()) } } class AdmobFragment : AdFragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val rootView = inflater.inflate(R.layout.fragment_admob, container, false) val adView = rootView.findViewById<AdView>(R.id.adView) adView.loadAd(AdRequest.Builder().build()) return rootView } companion object { fun newInstance() = AdmobFragment() } } class AdmobProvider : AdProvider { override fun init(context: Context) { MobileAds.initialize(context, BuildConfig.AD_APP_ID) } override fun newViewHolderInstance(parent: ViewGroup): AdViewHolder = AdmobViewHolder(parent) override fun newFragmentInstance(): AdFragment = AdmobFragment.newInstance() }
Admobの設定でApp IDと各広告のUnit IDが必要になります。これらはGitHubに入れたくないため、Googleで用意されているテスト用のデータをプロジェクトの gradle.properties
で設定し、本番用のデータを ~/.gradle/gradle.properties
に設定することで上書きます。
ADMOB_ID=admob_id # Test ID ADMOB_UNIT_ID_MAIN=ca-app-pub-3940256099942544/6300978111 ADMOB_UNIT_ID_SUB=ca-app-pub-3940256099942544/6300978111
この設定をbuild.gradleで読み込み、AndroidManifet/BuildConfig/string resourceに埋め込みます。
android { compileSdkVersion 28 defaultConfig { minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" manifestPlaceholders = [ ad_app_id: "$ADMOB_ID" ] buildConfigField "String", "AD_APP_ID", "\"$ADMOB_ID\"" resValue "string", "ad_unit_id_main", "$ADMOB_UNIT_ID_MAIN" resValue "string", "ad_unit_id_sub", "$ADMOB_UNIT_ID_SUB" ... }
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.phicdy.sample.admob"> <uses-permission android:name="android.permission.INTERNET" /> <application> <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="${ad_app_id}" /> </application> </manifest>
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.google.android.gms.ads.AdView xmlns:ads="http://schemas.android.com/apk/res-auto" android:id="@+id/adView" android:layout_width="wrap_content" android:layout_height="wrap_content" ads:adSize="BANNER" ads:adUnitId="@string/ad_unit_id_main" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
次にDIの設定です。 AdmobProvider
を AdProvider
の実装として提供します。
@Singleton @Component( modules = [AdModule::class] ) interface AdComponent { fun adProvider(): AdProvider @Component.Factory interface Factory { fun create(): AdComponent } } @Module class AdModule { @Provides fun provideAdProvider(): AdProvider = AdmobProvider() }
appモジュール
appモジュールでは AdComponent
を利用して AdProvider
を提供します。これでfeature_hogeモジュールでは @Inject
するだけでAdProviderが使えるようになります。
@Singleton @Component( modules = [ AndroidInjectionModule::class, AppModule::class, ActivityModule::class, AdComponentModule::class ] ) interface AppComponent : AndroidInjector<SampleApplication> { @Component.Factory abstract class Factory : AndroidInjector.Factory<SampleApplication> } @Module object AdComponentModule { @JvmStatic @Provides @Singleton fun provideAdProvider( adComponent: AdComponent ): AdProvider { return adComponent.adProvider() } @JvmStatic @Provides @Singleton fun provideAdComponent() = DaggerAdComponent.factory().create() }
おわりに
書き終わって思いましたが、これはAdmobに限らずTrackerなど使う側が実装を知らずに利用したい多くのケースで流用できると思います。マルチモジュールにすることでfeatureモジュールがfirebase-adsなどに依存しなくて済むし、差分ビルドが早くなる恩恵も受けられます。