kubell Creator's Note

ビジネスチャット「Chatwork」のエンジニアのブログです。

ビジネスチャット「Chatwork」のエンジニアのブログです。

読者になる

Androidアプリ開発で、開発者用画面にサクッとアクセスしたい

この記事はkubell Advent Calendar 2025の22日目の記事です。

こんにちは、kubellのAndroidアプリ開発グループでAndroidエンジニアをしている大西です。

最近は3Dプリンターにハマっていて、デスク周りの便利グッズを自作しています。小さな不便を解消するのって、地味に楽しいですよね。

さて、今回はそんな小さな不便の解消として、開発者用画面へのアクセスを楽にした話をします。

皆さんのプロジェクトには「開発者用画面」ってありますか?API環境の切り替えやFeature Flagの設定、ローカルキャッシュのリセットなど、開発やテストを便利にする機能を備えた画面のことです。

私が開発に携わっているChatworkにもあるんですが、これがまあアクセスしにくい場所にあるんですよね。

そこで「もっとサクッとアクセスできないかな?」と思い、いくつかのアプローチを検討してみることにしました。

目次

理想の条件

まずはどういった状態が開発者用画面にアクセスするのに理想的かを定義してみました。

  • アクセス速度:できるだけ少ないステップでアクセスできる
  • 操作の統一性:エミュレータと実機で同じ操作ができる
  • 画面への影響:テスト中の画面を邪魔しない(スクショ撮影やテストフローを中断しない)
  • チーム内での発見性:新しく入ったメンバーも簡単に見つけられる

この4つの条件を軸に、複数のアプローチを検討してみました。

方法1: フローティングボタン

最初に思いついたのがこれです。画面上にボタンを常駐させて、タップしたら開発者用画面に飛ぶ方法です。

Scaffold(
    modifier = Modifier.fillMaxSize(),
    floatingActionButton = {
        FloatingActionButton(
            onClick = {
                startActivity(Intent(this@MainActivity, DebugMenuActivity::class.java))
            }
        ) {
            Icon(
                imageVector = Icons.Filled.Settings,
                contentDescription = "Debug Menu"
            )
        }
    }
) { innerPadding ->
    MainScreen(modifier = Modifier.padding(innerPadding))
}

良いところ

  • 1タップでアクセスできる。最速。
  • 「ここに開発者用ボタンあるよ」って一目でわかる

微妙なところ

  • 画面に常に居座る
  • オーバーレイ権限が必要

1タップでアクセスできるのは魅力的なんですが、「常に画面にいる」のがどうしても気になりました。 スクショを撮るたびにボタンが映り込んでしまう上に、手動テスト中に誤タップしてしまう可能性もあります。さらにはVRTなどで差分として検出されてしまう可能性もあるため、やはり避けたいところです。


方法2: シェイク検知

次に考えたのが、シェイク検知を使う方法です。端末をシェイクすることで開発者用画面へアクセスするというアプローチで、Hyperion-Android などのライブラリで実現できます。

良いところ

  • 導入が簡単
  • シェイクでメニューが出るのは直感的

微妙なところ

  • エミュレータでシェイクできない問題

実機だとシェイクすればいいんですが、エミュレータだとExtended Controlsを開いて「Virtual sensors」からシェイクを送る必要があり、結局のところ、普通に画面遷移するより手数が増えてしまいます。

私は開発中エミュレータを使うことが多いので、この時点でちょっと厳しいなと思いました。

また、「開発者用画面を開きたいだけ」という目的に対して、機能がリッチすぎるという側面もあります。便利な面もありますが、シンプルさを求める場合には少し複雑に感じるかもしれません。

方法3: 通知

3つ目は通知を使う方法。アプリ起動時にステータスバーに通知を出しておいて、タップしたら開発者用画面に遷移する、というシンプルなやつです。

val notification = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_debug)
    .setContentTitle("Debug Menu")
    .setContentText("タップして開発者用画面を開く")
    .setContentIntent(pendingIntent)
    .setOngoing(true)
    .build()

良いところ

  • 通知シェードを開く → タップ、の2ステップでアクセスできる
  • エミュレータでも実機でも同じ操作
  • 画面を邪魔しない

微妙なところ

  • 通知シェードを開く1ステップが必要(1タップではない)
  • 通知がたくさんあると埋もれる

「通知シェードを開く」という1ステップが増えますが、エミュレータでも実機でも同じ操作でアクセスできるのが良いなと思いました。シェイクと違って、操作が安定していますし、どの画面にいてもすぐに開発者用画面にアクセスすることができます。

方法4: Quick Settings Tile

最後は、通知シェードのクイック設定にタイルを追加する方法。Wi-FiとかBluetoothのアイコンが並んでるあの場所に、自作のタイルを追加できます。

import android.annotation.TargetApi
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import android.service.quicksettings.TileService
import androidx.annotation.RequiresApi

@RequiresApi(Build.VERSION_CODES.N)
class DebugTileService : TileService() {
    @TargetApi(34)
    override fun onClick() {
        super.onClick()
        val intent = Intent(this, DebugMenuActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(
            this,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        startActivityAndCollapse(pendingIntent)
    }
}

良いところ

  • システム標準のUIに馴染む
  • 通知シェードから1タップでアクセス

微妙なところ

  • 自分でタイルを追加する必要がある

Quick Settings Tileはアプリをインストールしただけでは表示されず、ユーザーが「タイルを編集」からドラッグして追加する必要があります。新しくチームに入った人はこの機能の存在に気づきにくいですし、手間も増えてしまいます。

比較してみた

方法 アクセス エミュレータ 画面占有 導入・運用の手間 発見しやすさ
フローティングボタン ◎ 1タップ × 邪魔
シェイク検知 ○ シェイク △ 面倒
通知 ○ 2ステップ ◎ 自動表示
Quick Settings ○ 2ステップ △ 各自追加 △ 気づかれにくい

どれがいいか?

私は通知を使う方法が良いと思います。

最大の理由は「エミュレータと実機で操作が統一される」ということです。実際に開発していると、エミュレータと実機を何度も行き来することになるんですが、そのたびに操作方法が変わるのは思った以上に煩雑です。通知シェードなら、どちらでも「スワイプダウン → タップ」で済むというシンプルさが、積み重なると大きなストレス軽減につながるかなと思いました。

さらに、フローティングボタンと違い画面を占有しないので、テスト作業中に画面が邪魔にならないのも嬉しいポイントで、スクショを撮るときや手動テストの流れを妨げません。

実装してみる

せっかくなので、実装方法も紹介します。

ポイントは Debug Buildでのみ通知を表示する ことです。src/debug/ に配置すれば、Release Buildには含まれません。

ディレクトリ構成

app/src/
├── main/           # 共通コード
├── debug/          # Debug Buildのみ ← ここに置く
│   ├── AndroidManifest.xml
│   └── java/.../debug/
│       ├── DebugMenuNotificationInitializer.kt
│       └── DebugMenuNotification.kt
└── release/

App Startupで起動時に通知を表示

アプリ起動時に自動で通知を出したいので、App Startupライブラリを使います。

class DebugMenuNotificationInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        DebugMenuNotification.show(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}

Debug BuildのManifestに、App Startupの初期化に登録します。こうすることで、アプリ起動時に自動でこのInitializerが実行され、通知が表示されます。

<!-- debug/AndroidManifest.xml -->
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name=".debug.DebugMenuNotificationInitializer"
        android:value="androidx.startup" />
</provider>

通知の実装

object DebugMenuNotification {
    private const val CHANNEL_ID = "debug_menu"
    private const val NOTIFICATION_ID = 1

    fun show(context: Context) {
        createChannel(context)

        val intent = Intent(context, DebugMenuActivity::class.java)
        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            intent,
            PendingIntent.FLAG_IMMUTABLE
        )

        val notification = NotificationCompat.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_debug)
            .setContentTitle("Debug Menu")
            .setContentText("タップしてデバッグ画面を開く")
            .setContentIntent(pendingIntent)
            .setPriority(NotificationCompat.PRIORITY_LOW)  // 音鳴らさない
            .setOngoing(false)                             // ユーザーが必要に応じて消せる
            .setAutoCancel(false)                          // タップしても通知を残す
            .build()

        NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
    }

    private fun createChannel(context: Context) {
        val channel = NotificationChannel(
            CHANNEL_ID,
            "Debug Menu",
            NotificationManager.IMPORTANCE_LOW
        ).apply {
            setShowBadge(false)  // アプリアイコンにバッジ出さない
        }
        context.getSystemService(NotificationManager::class.java)
            .createNotificationChannel(channel)
    }
}

これで簡単に開発者用画面にアクセスできるようになりました🎉

通知の設定のちょっとしたこだわり

開発中に邪魔にならないよう、いくつか工夫しています。

  • IMPORTANCE_LOW: 通知音もバイブも鳴らない。
  • setOngoing(false): ユーザーが必要に応じてスワイプで消せるようにする。邪魔になったら自分で削除できるようにするためです。
  • setShowBadge(false): アプリアイコンに赤い点を出さない。
  • setAutoCancel(false): 何度もアクセスしたいのでタップしても通知を残す。

おわりに

この記事では「開発者用画面へのアクセスを楽にする」という改善について話してきました。
たかが2ステップですが、何十回も繰り返される操作だからこそ、この小さな改善が大きな開発体験の向上につながります。同じストレスを感じている方は、ぜひこの方法を試してみてください!

Androidアプリ開発グループではこういった改善を日々おこなっています! もし興味がある方は以下の採用ページをご覧ください!

hrmos.co

他の職種でも募集しています!

エンジニア採用 | 株式会社kubell