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)が、アプリが実行時に行う処理になります。
AppScreen
のwhen
文を再掲(説明の都合上、すこし整形してます)し、ワークフローと比べてみます。
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
の確認で実施しています。hasPermission
がtrue
なら、(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内で完結させるほうが現実的だと思います。
コンテンツは随時追加していきます。