Jetpack ComposeでPermissionを取得したい

この記事をシェア
JetpackCompose一問一答

Jetpack Composeで実行時のパーミッションを取得するには、AccompanistのPermissionsライブラリを使います。このページでは、画像ギャラリー表示などに必要なREAD_EXTERNAL_STORAGEパーミッションを例に説明します。

準備

AndroidManifest

Jetpack Composeを使う場合に限りませんが、まずはAndroidManifestにアプリが必要とする権限を宣言します。

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Gradle

次に、AccompanistのPermissionsライブラリを使うため、build.gradle依存関係を追加します。

dependencies {
    implementation 'com.google.accompanist:accompanist-permissions:0.23.1'
}

AccompanistはJetpack Composeのバージョンに対応する複数のバージョンが用意されています。最新の対応状況はAccompanistのサイトで確認してください。記事執筆時点では、Jetpack Compose 1.0を使う場合はAccompanist 0.20.3を、Jetpack Compose 1.1を使う場合はAccompanist 0.23.1が最新となっています。

Composeの実装

ここから、実際にComposeで権限を取得するための処理について説明します。以下では、AppScreenというコンポーザブル関数が作成する画面が権限を必要としているとします。AppScreenはアプリの一番上の階層のUIでも、いくつかの子画面のうちの一つでも構いません。

また、Accompanistのバージョンは0.23.1で説明します。Permissions APIはExperimentalなので、仕様が変更される可能性があります。

PermissionState

権限を取得するAppScreenの実装は次のようにPermissionStateが処理の中心になります。なお、PermissionsライブラリはExperimental APIなので、@OptIn(ExperimentalPermissionsApi::class)アノテーションをつけます。

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun AppScreen() {
    val permissionState = rememberPermissionState(Manifest.permission.READ_EXTERNAL_STORAGE)
    when {
        permissionState.hasPermission -> Text("Granted!")
        permissionState.shouldShowRationale -> PermissionRationaleDialog {
            permissionState.launchPermissionRequest()
        }
        permissionState.permissionRequested -> Text("Denied...")
        else -> SideEffect {
            permissionState.launchPermissionRequest()
        }
    }
}

PermissionStateオブジェクトは、rememberPermissionState()で取得します。この引数に、要求する権限を指定します。PermissionStateオブジェクトが権限の取得状態を保持し、変更があったときに再コンポーズが行われます。

PermissionStateクラスには、状態を判別するためのいくつかのパラメータがあります。これらを使って、権限取得状態に合わせた画面表示を行います。

hasPermissinは、権限取得済みの場合にtrueになります。trueの場合は、この画面で本来表示したい画面(画像ギャラリーの表示など、権限を利用して表示したい画面)を呼び出します。上の例では”Granted”というテキストを表示していますが、これを任意のコンポー座ブル関数に置き換えます。

shouldShowRationaleは、権限を必要とする理由をユーザーに説明することが求められる場合にtrueになります。ざっくり言うと、ユーザーが1回目に権限の要求を拒否した場合にtrueになります。trueの場合は、アプリが権限を必要とする理由を表示します。ここではダイアログを別関数で定義して呼び出しています。また、ダイアログをOKで閉じた場合に、launchPermissionRequest()で権限を要求しています。ダイアログの実装は別途説明します。

permissionRequestedは、ユーザーに対して権限を要求済みの場合にtrueになります。上の例のようにwhenの条件を構成すると、権限が必要な理由を説明したにもかかわらずユーザーが拒否した場合に、ここに来ます。したがって、権限が取得できなかった場合の画面を呼び出します。上の例では単に”Denied”とテキストを表示していますが、実際のアプリでは、権限がないためアプリの機能が制限されていることを説明することになります。

最後に、上記のどの条件にも当てはまらない場合は、権限の要求を行います。launchPermissionRequest()で権限を要求します。この関数はComposeのスコープ外で作用を発生させるので、SideEffectブロックで呼び出します。具体的には、いったんアプリがバックグラウンドに移動し、Android Systemが権限確認のダイアログを表示します。ユーザーがダイアログに応答すると、再度アプリに処理が戻ります。

以上が処理の流れです。1行1行の役割が大きいので説明が長くなりましたが、一つのwhen文で権限取得の処理を書けるのでとても便利です。

Rationaleのダイアログ

上の例で出てきたPermissionRationaleDialogは、以下のように簡単に書くことができます。

@Composable
fun PermissionRationaleDialog(onDialogResult: ()->Unit) {
    AlertDialog(
        text = { Text("Rationale") },
        onDismissRequest = {},
        confirmButton = {
            TextButton(onClick = onDialogResult) {
                Text("OK")
            }
        }
    )
}

Composeではダイアログが非常に簡単に書けます。従来のようにAlertDialogクラスのサブクラスを定義したりしなくていいのでとても楽です。

この例では適当に”Rationale”というテキストを表示していますが、ここに権限を必要とする具体的な理由を書きます。”OK”ボタンをクリックすると、コールバックが呼ばれます。呼び出し元では、コールバック関数内でlaunchPermissionRequest()を読んでいました。コールバック関数はUIスレッドで実行されるので、SideEffectを介さず、launchPermissionRequest()を直接呼び出して大丈夫です。

権限取得のワークフローとの対応

実装は以上です。ここからは、Developers Guideで推奨されている権限取得のワークフローへの対応を確認していきます。

権限取得時にアプリが従うべきワークフローは、Developers Guideに分かりやすい図が掲載されています。図中の(4)~(8)が、アプリが実行時に行う処理になります。

AppScreenwhen文を再掲(説明の都合上、すこし整形してます)し、ワークフローと比べてみます。

when {
    permissionState.hasPermission -> {  // (4), (7)
        Text("Granted!")  // (8a)
    } 
    permissionState.shouldShowRationale -> { // (5a)
        PermissionRationaleDialog {  // (5b)
            permissionState.launchPermissionRequest()  // (6)
        }
    }
    permissionState.permissionRequested -> Text("Denied...")  // (8b)
    else -> SideEffect {
        permissionState.launchPermissionRequest()  // (6)
    }
}

(4)の権限の有無確認は、hasPermissionの確認で実施しています。hasPermissiontrueなら、(8a)で権限を必要とする処理を実施します。

(5a)の理由説明は、shouldShowRationaleの確認が対応しています。(5b)の説明表示はダイアログで行い、(6)の権限要求は`ダイアログのコールバックで実施しています。

(5a)から直接(6)へ移動するパスは、else節のlaunchPermissionRequest()が対応しています。

(6)で権限を要求すると、制御がアプリからいったん離れるため、ワークフローも途切れています。次にアプリに制御が戻ってきたときは、when文の最初から再度実行されるので、(7)の権限確認は(4)と同じく最初のhasPermissionの確認で実現されます。ここで権限が取得できていれば(8a)へ、取得できていなければ最終的には(8b)にたどり着きます。

補足

複数の権限を取得する場合

上の例は、取得したい権限が一つだけの場合でした。2つ以上の権限をまとめて取得したい場合は、MultiplePermissinosStateを利用します。

Activity側での従来の実装は可能か?

Jetpack Composeアプリでも、従来と同じようにActivity側で権限取得の処理を記述することも可能ではあります。ただし、コンポーザブル関数はActivityの外側に記述するのが一般的なため、UIと連携しながら権限取得や説明の表示を行うには、コンポーザブルとActivityとで状態を共有する必要が出てきます。コンポーザブル階層が深くなってくるとこれも状態の共有も大変になってきますので、Composeを使うのであれば、権限取得もCompose内で完結させるほうが現実的だと思います。

JetpackCompose一問一答

Jetpack Compose一問一答

コンテンツは随時追加していきます。

この記事をシェア