Fragmentで呼び出し元に結果を伝える
最終更新日:2020-07-10
注:Fragment:1.3.0-alpha04
で onActivityResult()
などがdeprecatedとなり、下記の方法は使えなくなりました。古いアプリをメンテナンスする方のために、記事は残します。
新しい方法
フラグメントで呼び出し元に結果を返すには、 setFragmentResult
と setFragmentResultListener
を使います。この方法は Fragment:1.3.0-alpha04
以降で使用可能です。
呼び出し元に結果を返す
まずは呼ばれた側です。呼び出し元のフラグメントに結果を返すには、次のように parentFragmentManager
の setFragmentResult
を呼びます。
//データの入れ物
val data = Bundle()
data.putString("text", text)
// FragmentManager経由で結果を伝える
parentFragmentManager.setFragmentResult("input", data)
第1引数にはリクエストコードに相当する文字列を指定します。
結果を受け取る
次に呼んだ側で結果を受け取る方法です。 onCreate()
あたりで、 setFragmentResultListener()
でリスナーをセットします。これは拡張関数なので使用する際はktx版を追加しておきます。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener("input") { key, data ->
val text = data.getString("text", "")
// 結果を使った処理
}
}
リスナーのセットにはLifecycleの仕組みが使われているので、リスナーの取り外しの処理は不要です。
古い方法
Activityでは、startActivityForResult()
で次のActivityを呼び出すと、後で結果を取得することができました。Fragmentでは次のように実装することで、呼び出し元に結果を伝えることができます。
- 呼び出されるFragmentオブジェクトのsetTargetFragment()で、呼び出し元のFragmentオブジェクトをセットする
- 呼び出された側で結果を伝える場面で、getTargetFragment()で、呼び出し元のFragmentオブジェクトを取得する
- Intentに結果を詰め、呼び出し元のFragmentオブジェクトのonActivityResult()を呼ぶ
- 呼び出し元で
onActivityResult()
を実装し、結果を受け取ったときの処理を記述する
やや長いですが、順にみていきましょう。
呼び出されるFragmentオブジェクトのsetTargetFragment()で、呼び出し元のFragmentオブジェクトをセットする
setTargetFragment()メソッドで、呼び出し元のFragmentオブジェクトとリクエストコードをセットできます。リクエストコードは結果を伝えるときに使用します。
Fragmentオブジェクトを作る際にまとめてセットできるよう、newInstance()メソッドで次のように実装するとよいでしょう。
public static NextFragment newInstance(Fragment target, int requestCode) {
NextFragment fragment = new NextFragment();
fragment.setTargetFragment(target, requestCode);
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
呼び出された側で結果を伝える場面で、getTargetFragment()で、呼び出し元のFragmentオブジェクトを取得する
setTargetFragment()でセットされた呼び出し元のFragmentオブジェクトはgetTargetFragment()で取得できます。リクエストコードはgetTargetRequestCode()で取得できます。
Intentに結果を詰め、呼び出し元のFragmentオブジェクトのonActivityResult()を呼ぶ
入力内容などの結果はIntentオブジェクトを生成し、それに詰めます。単純に成功/キャンセルだけであればonActivityResult()の第2引数だけで表現できます。
例えば、保存ボタンを押すとEditTextの内容を結果として伝えるには次のようなコードになります。
// 引数は入力された文字列とします
void submit(String inputText) {
Fragment target = getTargetFragment();
if (target == null) { return; }
Intent data = new Intent();
data.putExtra(Intent.EXTRA_TEXT, inputText);
target.onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, data);
}
呼び出し元ではonActivityResult()を実装し、結果を受け取ったときの処理を記述する
呼び出し側のFragmentでonActivityResult()が呼ばれるので、呼び出し側でこのメソッドを実装します。
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_INPUT_NAME:
if (resultCode != Activity.RESULT_OK) { return; }
String name = data.getStringExtra(Intent.EXTRA_TEXT);
Log.v("FirstFragment", "入力された名前は" + name);
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
DialogFragmentはFragmentを継承しているので、この一連の手続きはDialogFragmentでも同様に使えます。これにより、ダイアログと呼び出し元の結合度が下がり、再利用性が高まります。
onActivityResult()を呼ぶのは邪道では?
Fragmentからの結果なのにonActivityResult()
を呼ぶのは邪道なのでは?と感じる方もいるでしょう。実はsetTargetFragment
のJavadocに答えが書いてあります(v25で確認)。
/**
* Optional target for this fragment. This may be used, for example,
* if this fragment is being started by another, and when done wants to
* give a result back to the first. The target set here is retained
* across instances via {@link FragmentManager#putFragment
* FragmentManager.putFragment()}.
*
* @param fragment The fragment that is the target of this one.
* @param requestCode Optional request code, for convenience if you
* are going to call back with {@link #onActivityResult(int, int, Intent)}.
*/
public void setTargetFragment(Fragment fragment, int requestCode) {
mTarget = fragment;
mTargetRequestCode = requestCode;
}
「onActivityResultでコールバックするなら、requestCodeを使うと便利だよ」と書いてあります。