アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

【Android】ActivityResultContractってなんだ

startActivityForResult , onActivityResult は廃止されます。これに関しては既に内部的には ActivityResultContract 等を使用するように置き換えられています。
f:id:matsudamper:20201102012944p:plain

用途

Intentを作成して起動( startActivityForResult ) -> 結果を受け取るもの( onActivityResult )は今後これを使います。

使い方

ActivityやFragmentでとあるファイルをダウンロードして書き込みを行いたいとしたら、以下のようにします。
(今はScoped Storageの話は忘れてください)

private val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { it : ActivityResultCallback ->
   if (it) {
       // TODO: ダウンロード -> 書き込み処理
   }
}

fun download() {
    launcher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}

解説

ActivityResultContract , ActivityResultLauncher , ActivityResultCallback の3つが登場します。

registerForActivityResultActivityResultContractActivityResultCallback を指定します。 すると戻り値で ActivityResultLauncher が取得できます。

ActivityResultLauncher に対して launch を行うと ActivityResultContracts で指定された処理が行われ、 ActivityResultCallback に結果がやってきます。

実装を見る

ActivityResultLauncher はライブラリ側がよしなにやってくれるので今回は解説しません。
使用するだけなら ActivityResultContractsActivityResultCallback の実装だけわかっていれば大丈夫です。

ActivityResultCallback

ActivityResultContracts は良く使われるであろうものは既に定義されています。 ActivityResultContracts.RequestPermission() がそうです。

以下の3つ、最低2つを実装してあげるだけで大丈夫です。 parseResult を見ると requestCode という概念は存在しません。内部で使用されている ActivityResultRegistry がよしなにやってくれるので requestCode の管理から開放されます。

/**
* Iで引数を指定する
* Oで戻り値を指定する
**/
public abstract class ActivityResultContract<I, O> {

    // Activity#setResult()で結果を返してくれるIntentを生成する
    public abstract @NonNull Intent createIntent(
        @NonNull Context context,
        @SuppressLint("UnknownNullness") I input
    );

    // Intentを「O」にパースして返す
    @SuppressLint("UnknownNullness")
    public abstract O parseResult(
        int resultCode,
        @Nullable Intent intent
    );

    // デフォルトでnullなので実装しなくても良い
    // ここで結果を返すとcreateIntentが呼ばれす、そのまま結果を返す。
    public @Nullable SynchronousResult<O> getSynchronousResult(
            @NonNull Context context,
            @SuppressLint("UnknownNullness") I input
    ) {
        return null;
    }
}

ActivityResultCallback

ActivityResultCallback の結果の「O」が渡されるだけです。

public interface ActivityResultCallback<O> {
    void onActivityResult(@SuppressLint("UnknownNullness") O result);
}

おまけ

使用する側でlaunchするわけですが、使用する側が使用するPermissionを意識するのって微妙じゃないですかね。

private val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { it : ActivityResultCallback ->
   if (it) {
       // TODO: ダウンロード -> 書き込み処理
   }
}

fun download() {
    launcher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}

というわけで、この様に書き換えてみました。普通に関数のように使用できます。

private val doDownload = object {
    private val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
    }

    operator fun invoke() {
        launcher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
    }
}

Scoped Storage 等で特定OSバージョンで権限要らないなとなった場合は以下のように書けます。

private val doDownload = object {
    private val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
        if (it) {
            download()
        } else {
            // TODO: Error message
        }
    }

    operator fun invoke() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            download()
        } else {
            launcher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
        }
    }

    private fun download() {
        // TODO
    }
}