Androidアプリのデバッグメニューを作ろう

こんにちは。
Androidエンジニアの@syarihuです。

Androidアプリを作っていて、エンドポイントを切り替えたり、ユーザの状態1を変えたりしたいときありますよね。

エンドポイントの切り替えは、ビルドのやり直しや各環境のapkをインストールするなど手間が発生してしまいます。
アプリをビルドし直したりせずに、エンドポイントなどを切り替えられたら嬉しいですよね。

そこで今回は debug-alter というライブラリを利用して、デバッグ時のみ利用可能なデバッグメニューを作成する方法を紹介します。

 

debug-alterとは

debug-alter は、Javaに対するアスペクト指向プログラミングのための拡張である AspectJ を利用してデバッグ時のメソッドの返り値を任意の値に変更できるように作られたAndroid向けのライブラリです。

debug-alterの詳細については開発者本人が書いたQiitaがあるので、そちらを参照してください。

コードを変更せずにデバッグメニューでAndroidアプリの動作を変更する – Qiita
https://qiita.com/takahirom/items/4067e7bb0c3f614dc749

 

debug-alterの良いところ、良くないところ

私が個人的に感じている debug-alter の良いところ・良くないところは下記の通り。

良いところ

  • @DebugReturn をつけたメソッドを任意の値に差し替え可能
  • プロダクションコードにはアノテーションをつけるだけでよいため、デバッグでプロダクションコードを汚さなくて済む
  • OSSで開発されている
  • Jake Wharton氏が作り、わりと有名だった Hugo というライブラリでもAspectJの仕組みを利用していたという安心感

良くないところ

  • 差し替え対象のメソッド名を自分で書かないといけない
    • 本当は自動生成とかしてくれると良さそう
  • kaptと相性が悪い
    • これについては回避方法があるので次で説明

 

debug-alterを導入する

Money ForwardのAndroidアプリへのdebug-alterの導入を例に説明していきます。

build.gradleの設定

app/build.gradleに次のように設定します。

 buildscript {
     dependencies {
        classpath("com.github.takahirom.debug.alter:plugin:${debug_alter_version}") {
            exclude module: 'kotlin-gradle-plugin'
        }
}

apply plugin: 'com.github.takahirom.debug.alter'

 dependencies {
    // Debug Alter Annotation
    implementation "com.github.takahirom.debug.alter:annotation:${debug_alter_version}@aar"
}

pluginの適用をした時点でdebug-alter自体は利用できます。

しかし、複数のFlavorが存在する場合はFlavorを切り替えるとアノテーションが参照できなくなってしまいます。別途アノテーションのみが含まれるライブラリを導入することで、Flavorを変更してもアノテーションが参照できるようにしています。

また、debug-alterはkaptと非常に相性が悪いです。kaptを利用しているアプリ2で、アプリのKotlinバージョンとdebug-alterのKotlinバージョンに差異があると、kaptのビルドタスクでエラーが発生します。

実際に起きた例として、Money ForwardのAndroidアプリのKotlinのバージョンを上げたところkaptのビルドタスクでエラーが発生してビルドが通らなくなりました。

kaptのビルドタスクでエラーが発生するのを回避するため、classpathの部分にexclude module: 'kotlin-gradle-plugin'を追記しkotlin-gradle-pluginを除外することで、debug-alterのkotlin-gradle-pluginに影響されないようにしました。

デバッグ用のApplicationクラスを作成

デバッグ時に差し替える値をセットする処理とデバッグメニューを開くための通知を送信する、デバッグ用のApplicationクラスを作成します。

class DebugApplication : CustomApplication() {
    override fun onCreate() {
        // 差し替え用のアイテムをセットしておく
        resetDebugAlterItems()
        super.onCreate()
        // デバッグメニューに遷移するための通知を表示
        showNotification()
    }
     <application
        android:name="com.moneyforward.android.DebugApplication"

独自のApplicationクラスが存在していてonCreateで何らかの初期化処理を行っている場合は、onCreateの直前に差し替え用のアイテムをセットすることで、値を差し替えたいメソッドがonCreate内で呼び出されていても値が差し替わるようになります。

またデバッグメニューを開くための通知を送信することで、プロダクトの画面に影響することなく通知からデバッグメニューへ遷移できるようにします。

 

差し替え対象のメソッドにアノテーションをつける

debug-alterでは@DebugReturnがついたメソッドが任意の値を差し替える対象になるため、値を差し替えたいメソッドにアノテーションをつけます。

たとえば、APIのエンドポイントを返却するgetApiEndPointというメソッドをデバッグ時に任意の値に差し替えたい場合は次のように実装します。

@DebugReturn
public static String getApiEndPoint() {

プロダクションコードにはこのアノテーションを実装するだけでよいので、とてもよいですね。

デバッグ用の値に差し替える

debug-alterでは@DebugReturnがついたメソッドを呼び出した際に、次の条件に一致する場合にメソッドの通常の返り値ではなくDebugAlterItemのgetメソッドから取得した値を返却します。

  • @DebugReturnがついているメソッドのkey3とDebugAlterのインスタンスにセットされているDebugAlterItemのkeyが一致している
  • DebugAlterItemのisAlterメソッドの返り値がtrueである

上記の判定に利用するDebugAlterItemのリストを簡単に生成できるようにするため、Helperクラスを作成します。
まずは、Helperクラスの中にメソッドのkeyとメソッドが返却する型を定義したenumクラスを作成しましょう。

class DebugAlterHelper(private val context: Context) {
    private enum class AlterItemInfo(
            val keyResId: Int,
            val alterItemType: AlterItemType
    ) {
        API_END_POINT(R.string.key_api_end_point, AlterItemType.STRING),
        PREMIUM(R.string.key_premium, AlterItemType.BOOLEAN)
    }

    private enum class AlterItemType {
        STRING,
        BOOLEAN
    }
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="key_api_end_point">getApiEndPoint</string>
    ...

差し替える値はPreferenceに保存します。Preferenceのkeyでもそのまま扱えるようにstringリソースに記述し、Preferenceに保存する型はAlterItemTypeという別のenumクラスを作成して、AlterItemInfoのプロパティとしてセットしておきます。

次に、DebugAlterItemのオブジェクトを生成するメソッドを用意します。

class DebugAlterHelper(private val context: Context) {
    ...
    private fun getStringItem(@StringRes keyResId: Int): DebugAlterItem<String> {
        val preference = PreferenceManager.getDefaultSharedPreferences(context)
        return object : DebugAlterItem<String>(context.getString(keyResId)) {
            override fun isAlter(): Boolean = preference.contains(key)
            override fun get(): String? = preference.getString(key, null)
        }
    }
    ...

DebugAlterItemのコンストラクタは引数としてkeyを受け取ります。
リソースIDを元に取得した文字列をkeyとしてDebugAlterItemのオブジェクトを生成します。

isAlterメソッドはPreferenceに対象のkeyが存在する場合にtrueを返却することで、何もない場合は通常のメソッドを実行します。
getStringItemメソッドはPreferenceに保存している値がStringだった場合に利用するメソッドです。もしPreferenceがString以外の型の場合は、その型のDebugAlterItemを返却するメソッドを別途作成する必要があります。

たとえばBooleanの場合は、DebugAlterItemを返却するメソッドを作成します。作成したメソッドを利用して、enumを元にDebugAlterItemのリストを返却するメソッドを作成しましょう。

class DebugAlterHelper(private val context: Context) {
    ...
    fun getAlterItemList(): List<DebugAlterItem<*>> {
        return arrayListOf<DebugAlterItem<*>>().apply {
            AlterItemInfo.values().forEach {
                when (it.alterItemType) {
                    AlterItemType.STRING -> add(getStringItem(it.it.keyResId))
                    AlterItemType.BOOLEAN -> add(getBooleanItem(it.it.keyResId))
                }
            }
        }
    }
    ...

AlterItemInfoが持っているAlterItemTypeを利用して、対象の型のDebugAlterItemが返却されるようにします。
作成したgetAlterItemListメソッドを利用して生成したリストを、DebugAlterのsetItemsメソッドに渡します。

    private fun resetDebugAlterItems() {
        val debugAlterHelper = DebugAlterHelper(this)
        DebugAlter.getInstance().setItems(
                debugAlterHelper.getAlterItemList()
        )
    }

これで @DebugReturn がついているメソッドを実行した際、条件が一致すればPreferenceにセットした値が返却されるようになりました。

 

デバッグメニューを作る

デバッグ時に差し替えたい値を設定するメニュー画面を作成し、デバッグ用のAndroidManifestに追加します。

     <application
         android:name="com.moneyforward.android.DebugApplication"
         ... >
        <activity
            android:name="com.moneyforward.android.DebugMenuActivity"
            android:label="デバッグメニュー" />
    </application>

最後に、作成したデバッグメニューを通知がタップされたときに起動するように実装します。

 

デバッグメニューを使ってみる

Money ForwardのAndroidアプリにデバッグメニューを実装したので、実際にプレミアム状態をデバッグメニューで切り替えてみます。

通知からデバッグメニューを起動し、設定を変更後すぐに反映されているのがわかりますね。
これでデバッグメニューの完成です!

 

まとめ

debug-alterを利用することで、プロダクションコードにはアノテーションをつけるだけでデバッグ時に値を差し替えられるようになりました。

デバッグメニューが実装されたアプリをインストールしてもらうことで、アプリエンジニアの手間を軽減するだけでなく、サーバーサイドのエンジニアが各環境のアプリを確認したい際にも役に立つことでしょう。
ぜひ活用していきましょう。

 

最後に

マネーフォワードでは、エンジニアを募集しています。
ご応募お待ちしています。

【採用サイト】
マネーフォワード採用サイト
Wantedly | マネーフォワード

【マネーフォワードのプロダクト】
自動家計簿・資産管理サービス『マネーフォワード』
Web
iPhone,iPad
Android

「しら」ずにお金が「たま」る 人生を楽しむ貯金アプリ『しらたま』
Web
iPhone,iPad

ビジネス向けクラウドサービス『MFクラウドシリーズ』
バックオフィス業務を効率化『MFクラウド』
会計ソフト『MFクラウド会計』
確定申告ソフト『MFクラウド確定申告』
請求書管理ソフト『MFクラウド請求書』
給与計算ソフト『MFクラウド給与』
経費精算ソフト『MFクラウド経費』
入金消込ソフト『MFクラウド消込』
マイナンバー管理ソフト『MFクラウドマイナンバー』
資金調達サービス『MFクラウドファイナンス』

メディア
くらしの経済メディア『MONEY PLUS』


  1. 通常会員、プレミアム会員など ↩︎

  2. Money ForwardのAndroidアプリは利用している ↩︎

  3. デフォルトではメソッド名がkeyになる。@DebugReturn("key")のようにkeyを変更することも可能 ↩︎

Pocket