🍦

プロダクトフレーバーを使った有料版アプリと無料版アプリの実装

2022/07/18に公開

はじめに

Androidアプリでプロダクトフレーバーを使って有料版アプリと無料版アプリの実装をしたので、手順を投稿します。管理ファイル数を減らす実装例と、無駄な依存関係を減らす実装例の2パターンを紹介しています。

関連する筆者の記事

  1. プロダクトフレーバーを使った有料版アプリと無料版アプリの実装(本記事)
  2. AndroidアプリのAdMobを使ったバナー広告の実装
  3. GitHub PagesでAdMobのapp-ads.txtを設定する

参考資料

本記事に関連するAndroidDevelopersのガイドは以下です。
https://developer.android.com/studio/build/build-variants

プロダクトフレーバー実装前の筆者の状態

  1. GooglePlayにアプリは未リリース
  2. プロダクトフレーバーを使って有料版アプリと無料版アプリをリリースしたい
  3. 無料版アプリには広告を搭載したい

1. プロダクトフレーバーの設定

1.1. アプリレベルのbuild.gradleにプロダクトフレーバーを追加する

アプリレベルのbuild.gradleにプロダクトフレーバーを追加します。筆者の場合、「plan」というフレーバーディメンションに、有料版アプリ向けの「pro」と無料版アプリ向けの「free」というプロダクトフレーバーを追加しています。設定は以上です。

app/build.gradle
android {
    ...
    
    flavorDimensions "plan"
    productFlavors {
        pro {
            dimension "plan"
            applicationIdSuffix ".pro"
            versionNameSuffix "-pro"
        }
        free {
            dimension "plan"
            applicationIdSuffix ".free"
            versionNameSuffix "-free"
        }
    }
}
...

2. プロダクトフレーバーを使った実装

2章ではプロダクトフレーバーを使った筆者の実装を紹介します。筆者はできるだけプロダクトフレーバー毎に個別ファイルを作らずに、main/ソースセット内でBuildConfigを使って有料版アプリと無料版アプリの実装を分けました。これにはファイルの多重管理を減らすメリットがあります。デメリットとしては、有料版アプリで不要なGoogleMobileAdsSDKの依存関係が追加されています。この不要な依存関係を抜く場合の実装例を3章で紹介します。

2.1. ソースセットを作成する

AndroidStudioではデフォルトでmain/ソースセットとディレクトリが作成されます。ここに、追加でプロダクトフレーバーのソースセットを作成します。筆者の場合、main/ソースセットで有料版アプリの実装を行い、free/ソースセットに無料版アプリ固有の実装を配置しています。とは言え、実際の差分実装はstring.xmlの文字列リソースのみで、他の無料版アプリ固有の実装は後述するBuildConfigを使って行っています。

ディレクトリ構成
Project/app/
├─ src/
│    ├─ free/
│    │    └─ res/
│    │        └─ values/
│    │            └─ string.xml
│    └ main/
│          ├─ java/zozooizozzoizioiiiooi.taps/
│          │    ├─ TitleActivity.kt
│          │    └─ TapsActivity.kt
│          └─ res/
│              ├─ layout/
│              │    ├─ activity_title.xml
│              │    └─ activity_taps.xml
│              └─ values/
│                  └─ string.xml
└─ build.gradle

ここでポイントとなるのは、アプリレベルのbuild.gradleでimplementationを使って、有料版アプリと無料版アプリに共有でGoogleMobileAdsSDKの依存関係が追加されている点です。有料版アプリではAdMobの広告は表示しませんが、無料版アプリと共通のファイルでGoogleMobileAdsSDKが使われているため、依存関係を抜くことができません。

app/build.gradle
...
dependencies {
    implementation 'com.google.android.gms:play-services-ads:21.0.0'
    ...
}

2.2. free/ディレクトリに無料版アプリ固有の実装を配置する

以下のstring.xmlの一つ目はmain/ソースセット内に配置された文字列リソースです。こちらのapp_nameはHOME画面に表示されるアプリ名で、通常はtapsです。これを二つ目の文字列リソースでプロダクトフレーバーが「free」の場合のみtaps freeになるようにしています。

main/res/values/string.xml
<resources>
    <string name="app_name">taps</string>
    <string name="taps_four">taps 4</string>
    <string name="taps_nine">taps 9</string>
    <string name="taps_pi">taps π</string>
    ...
</resources>
free/res/values/string.xml
<resources>
    <string name="app_name">taps free</string>
</resources>

2.3. main/ソースセットでBuildConfigを使って無料版アプリ固有の実装を行う

main/ソースセットのlayoutファイルには無料版アプリでのみ表示する広告やタイトルのfreeの文字を載せて非表示にしています。これらの要素をTitleActivity.ktの中でBuildConfigを使ってプロダクトフレーバーが「free」の場合にのみ表示するようにしています。有料版アプリと無料版アプリで共通のTitleActivity.ktとactivity_title.xmlでGoogleMobileAdsSDKが使われているため、有料版アプリから依存関係を抜くことができません。
こちらにはTitleActivityのみの実装を記載していますが、TapsActivityも同様の実装となります。

main/res/layout/activity_title.xml(概要)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout>
    <com.google.android.gms.ads.AdView
        android:id="@+id/adView"
	android:visibility="gone" />
    <TextView
        android:id="@+id/title"
        android:text="taps" />
    <TextView
        android:id="@+id/free"
        android:text="free"
        android:visibility="invisible" />
    ...
</androidx.constraintlayout.widget.ConstraintLayout>
main/java/zozooizozzoizioiiiooi.taps/TitleActivity.kt
package zozooizozzoizioiiiooi.taps
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.ads.AdRequest
...

class TitleActivity : AppCompatActivity() {
    private lateinit var binding: ActivityTitleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTitleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        if(BuildConfig.FLAVOR == "free") {
            binding.free.visibility = VISIBLE
	    binding.adArea.visibility = VISIBLE
            binding.adView.loadAd(AdRequest.Builder().build())
        }
    }
    ...
}

3. 有料版アプリで不要なGoogleMobileAdsSDKの依存関係を抜く方法

筆者は2章に記載した実装で、できるだけプロダクトフレーバー毎に個別ファイルを作らないようにしました。これによって、有料版アプリに不要なGoogleMobileAdsSDKの依存関係が追加されています。3章では、この不要な依存関係を抜く場合の実装例を紹介しています。

3.1. ディレクトリ構成とGoogleMobileAdsSDKの依存関係

有料版アプリで不要なGoogleMobileAdsSDKの依存関係を抜く場合のディレクトリ構成は以下になります。pro/ソースセットとfree/ソースセットに個別に同一のファイルが配置されており、2章のディレクトリ構成と比べてファイル数が多くなっていることが分かります。

ディレクトリ構成
Project/app/
├─ src/
│    ├─ pro/
│    │    ├─ java/zozooizozzoizioiiiooi.taps/
│    │    │    ├─ TitleActivity.kt
│    │    │    └─ TapsActivity.kt
│    │    └─ res/
│    │        ├─ layout/
│    │        │    ├─ activity_title.xml
│    │        │    └─ activity_taps.xml
│    │        └─ values/
│    │            └─ string.xml
│    ├─ free/
│    │    ├─ java/zozooizozzoizioiiiooi.taps/
│    │    │    ├─ TitleActivity.kt
│    │    │    └─ TapsActivity.kt
│    │    └─ res/
│    │        ├─ layout/
│    │        │    ├─ activity_title.xml
│    │        │    └─ activity_taps.xml
│    │        └─ values/
│    │            └─ string.xml
│    └ main/
│        └─ res/
│            └─ values/
│                └─ string.xml
└─ build.gradle

build.gradleでの依存関係の追加はfreeImplementationとします。これによってプロダクトフレーバーが「free」の場合のみにGoogleMobileAdsSDKの依存関係を追加することができます。

app/build.gradle
...
dependencies {
    freeImplementation 'com.google.android.gms:play-services-ads:21.0.0'
    ...
}

3.2. pro/ソースセットの有料版アプリの実装

有料版アプリではアプリ名をtapsとしています。また、2章で紹介した実装と比べてレイアウトリソースとTitleActivity.ktからAdMobの実装を抜いています。
こちらにはTitleActivityのみの実装を記載していますが、TapsActivityも同様の実装となります。

pro/res/values/string.xml
<resources>
    <string name="app_name">taps</string>
</resources>
pro/res/layout/activity_title.xml(概要)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView
        android:id="@+id/title"
        android:text="taps" />
    ...
</androidx.constraintlayout.widget.ConstraintLayout>
pro/java/zozooizozzoizioiiiooi.taps/TitleActivity.kt
package zozooizozzoizioiiiooi.taps
import androidx.appcompat.app.AppCompatActivity
...

class TitleActivity : AppCompatActivity() {
    private lateinit var binding: ActivityTitleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTitleBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
    ...
}

3.3. free/ソースセットの無料アプリの実装

無料版アプリではアプリ名をtaps freeとしています。レイアウトリソースとTitleActivity.ktにはAdMobの実装が追加されています。2章で紹介した実装と比べて、無料版アプリでのみ表示する広告やタイトルのfreeの文字を非表示にする必要がありません。
こちらにはTitleActivityのみの実装を記載していますが、TapsActivityも同様の実装となります。

free/res/values/string.xml
<resources>
    <string name="app_name">taps free</string>
</resources>
free/res/layout/activity_title.xml(概要)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout>
    <com.google.android.gms.ads.AdView
        android:id="@+id/adView" />
    <TextView
        android:id="@+id/title"
        android:text="taps" />
    <TextView
        android:id="@+id/free"
        android:text="free" />
    ...
</androidx.constraintlayout.widget.ConstraintLayout>
free/java/zozooizozzoizioiiiooi.taps/TitleActivity.kt
package zozooizozzoizioiiiooi.taps
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.ads.AdRequest
...

class TitleActivity : AppCompatActivity() {
    private lateinit var binding: ActivityTitleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTitleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.adView.loadAd(AdRequest.Builder().build())
    }
    ...
}

4. 実装結果

最後に有料版アプリと無料版アプリの実装結果を添付します。2章に記載した方法でも、3章に記載した方法でも、結果は同じになります。右に示した無料版アプリではAdViewの広告が表示されています。また、TitleActivityにはタイトルにfreeの文字が記載され、アプリバーに表示されるアプリ名もtaps freeとなっています。

以上です。ありがとうございました。本記事で無料版アプリに搭載したAdMobを使った広告の実装方法は以下の記事で紹介しています。
https://zenn.dev/zozooizozzoizio/articles/992d5a7864d009

筆者のアプリ

テンキーでの入力スピードを競うAndroidアプリを開発しました。こちらの記事で紹介したプロダクトフレーバーを使って有料版アプリと無料版アプリをリリースしています。無料版アプリには広告が表示されますが、機能は有料版と同じになっています。興味を持って頂けた方は是非プレイしてみてください。

https://play.google.com/store/apps/details?id=zozooizozzoizioiiiooi.taps.free

https://youtu.be/RHbiQxafGkQ

Discussion