Mokelab Blog

#30 2020年版、AndroidでFragmentの結果を呼び出し元に伝える

注意:本記事はアルファ版ライブラリを使用しています。製品のコードを書き換える場合は正式版がリリースされてからにしましょう。

Fragment:1.3.0-alpha04

Fragmentのバージョン1.3.0-alpha04が2020年4月29日にリリースされています。このバージョンから、 FragmentstartActivityForResult() / onActivityResult() / requestPermissions() / onRequestPermissionsResult() がdeprecatedになっています。

onActivityResult

deprecatedになったメソッドの中にonActivityResultも入っています。 このメソッドはフラグメントで呼び出し元に結果を伝える方法として使われてきました。

では、これからはどのようにして呼び出し元に結果を伝えればよいでしょうか?

setFragmentResult

まずは結果を伝える側です。新しいメソッドとしてFragmentManagersetFragmentResult() というメソッドが追加されています。第1引数にはリクエストコードに相当する文字列を指定し、第2引数で 結果データをBundle型で指定します。Intent型ではないので、既存コードは書き換えが必要になります。

//データの入れ物
val data = Bundle()
data.putString("text", text)

// FragmentManager経由で結果を伝える
parentFragmentManager.setFragmentResult("input", data)

setFragmentResultListener

続いて結果を受け取る側です。今まではonActivityResult()をオーバーライドし、 そこで処理を書いていましたが、これからはsetFragmentResultListenerを使います。 リスナーなので、onCreate()あたりでセットするとよいでしょう。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setFragmentResultListener("input") { key, data ->
        val text = data.getString("text", "")
        // 結果を使った処理
    }
}

第1引数はリクエストコードに相当する文字列を指定します。第2引数では結果を受け取ったときに実行するブロックを指定します。

なお、setFragmentResultListener()Fragmentの拡張関数として定義されており、中身は次のようになっています。

fun Fragment.setFragmentResultListener(
    requestKey: String,
    listener: ((resultKey: String, bundle: Bundle) -> Unit)?
) {
    parentFragmentManager.setFragmentResultListener(requestKey, this, listener)
}

FragmentManagersetFragmentResultListener()を呼んでいるだけですが、第2引数に LifecyclerOwnerとしてフラグメントを渡しています。これは、フラグメントが 開始状態(ON_START)になった時点で、未処理の結果処理を行い、 破棄された(ON_DESTROY)ときに自動でリスナーの解除を行うために使用しているようです。

// FragmentManagerはJavaで記述されています
LifecycleEventObserver observer = new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_START) {
            // once we are started, check for any stored results
            Bundle storedResult = mResults.get(requestKey);
            if (storedResult != null) {
                // if there is a result, fire the callback
                listener.onFragmentResult(requestKey, storedResult);
                // and clear the result
                setFragmentResult(requestKey, null);
            }
        }

        if (event == Lifecycle.Event.ON_DESTROY) {
            lifecycle.removeObserver(this);
            mResultListeners.remove(requestKey);
        }
    }
};

(リクエストキーに対するリスナーのセットを忘れると、結果のBundlemResultsから消されず、ずっとメモリ上に生存しちゃいますね。。。)

まとめ

Fragment:1.3.0-alpha04で変更となった、呼び出し元フラグメントに結果を伝える方法を紹介しました。 新方式は画面遷移にNavigationライブラリを使用している際に威力を発揮するので、正式版が出たら書き換えていきましょう。

本サイトではサービス向上のため、Google Analyticsを導入しています。分析にはCookieを利用しています。