Androidアプリ開発 Fragment FragmentからFragmentへのcallbackを実装する

2017年02月26日(編集2017年02月26日)
このエントリーをはてなブックマークに追加

Androidアプリ開発では、Fragmentでcallbackを利用することで、関連づいたActivityやFragmentに任意のデータや結果を渡すことができます。
この記事は、FragmentからFragmentへのcallbackの実装方法を記載した記事です。

環境はAndroid 7.1 (API level 25) です。

環境

  • OS X El Capitan
  • android sdk 25
  • Oracle jdk version 1.8.0_72
  • Android Studio 2.2.3

難易度

中級者向け

サンプルコード

Android-Basic-Technique-Demo

callback

callbackは、呼び出し先の関数の実行中に実行されるように、あらかじめ指定しておく関数のことです。
重要なプログラミングスキルですが、言葉だけでは内容が伝わりにくいテクニックです。

callbackの実装経験がない場合は、インターフェースを使って、クラスから他のクラスのメソッドを呼び出すという考え方を頭に入れておくと良いでしょう。

アプリ設計

サンプルとして、以下のようなActivityとFragmentの構成のアプリを作成します。

fig1. アプリ構成

FragmentA内でFragmentBを生成し、FragmentBからFragmentAにcallbackします。

作成するアプリの画面は以下です。

fig2. 画面(Activity + FragmentA)

画像をタッチして、画像フィルターを選択するダイアログを表示します。

fig3. DialogFragment(FragmentB)

フィルターを選択するとcallbackが実行されて画像表示画面に戻ります。

fig4. 画面(FragmentB callback + Activity + FragmentA)

画像表示画面と画像選択ダイアログを別々のFragmentで作成します。

実装

まずは画面のベースになるActivityを作成します。
クラス名はCallBackFromFragmentToFragmentActivityにします。

{project_folder}/CallBackFromFragmentToFragmentActivity.java
public class CallBackFromFragmentToFragmentActivity extends AppCompatActivity implements ImageFragment.OnFragmentInteractionListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call_back_from_fragment_to_fragment);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Begin the transaction
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        // Replace the contents of the container with the new fragment
        ft.replace(R.id.container, ImageFragment.newInstance());
        // or ft.add(R.id.your_placeholder, new FooFragment());
        // Complete the chang
        ft.commit();
    }
}
        

ImageFragmentを管理するだけのActivityです。
ActivityにFragmentを付加してます。

続いて、画面を表示するFragmentを作成します。クラス名はImageFragmentにします。
このImageFragmentは、別のFragmentのコールバックを受けるFragmentです。

{project_folder}/ImageFragment.java
public class ImageFragment extends Fragment {
    public static final int REQUEST_CODE_IMAGE_FILTER_DIALOG = 100;

    private ImageView imageView;

    private OnFragmentInteractionListener mListener;

    public static ImageFragment newInstance() {
        ImageFragment fragment = new ImageFragment();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_image, container, false);
        imageView = (ImageView) view.findViewById(R.id.image);
        imageView.setOnClickListener(image -> {
            openDialog();
        });
        return view;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    /**
     * Open ImageFilterDialogFragment
     */
    private void openDialog() {
        ImageFilterDialogFragment imageFragment = ImageFilterDialogFragment.newInstance();
        // 実装の解説1(jump)
       +imageFragment.setTargetFragment(this, ImageFragment.REQUEST_CODE_IMAGE_FILTER_DIALOG);
        imageFragment.show(getFragmentManager(), "dialog");
    }

    // 実装の解説2(jump)
    @Override
   +public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_CODE_IMAGE_FILTER_DIALOG:
                if (resultCode != Activity.RESULT_OK) {
                    return;
                }
                String[] imageFiltersName = getResources().getStringArray(R.array.image_filters_name);
                Toast.makeText(getActivity().getApplicationContext(),
                        "requestCode : " + requestCode + ", resultCode : " + resultCode + " " +
                                getString(R.string.image_filter_selected_msg, imageFiltersName[data.getIntExtra(ImageFilterDialogFragment.SELECTED_FILTER_NAME, 0)]),
                        Toast.LENGTH_LONG).show();
                break;
        }
    }

    public interface OnFragmentInteractionListener {
    }
}

        

■ 実装の解説

1. setTargetFragment

setTargetFragmentメソッドは、別のFragmentの結果を戻すために使用するメソッドです。

imageFragment.setTargetFragment(this, ImageFragment.REQUEST_CODE_IMAGE_FILTER_DIALOG);
        

第1引数は生成したFragment(ImageFilterDialogFragment)で利用するFragment(ImageFragment)を設定します。大抵の場合、this(呼び出し元のFragment)を設定します。

第2引数はリクエストコードを設定します。このリクエストコードはonActivityResultメソッドでも使用するので、定数にします。

public static final int REQUEST_CODE_IMAGE_FILTER_DIALOG = 100;
        

2. onActivityResult

onActivityResultメソッドは、デフォルトではstartActivityForResultメソッドの結果を受け取ります。
このサンプルでは、別のFragment(ImageFilterDialogFragment)から呼び出されるonActivityResultメソッドの結果を受け取ります。

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case REQUEST_CODE_IMAGE_FILTER_DIALOG:
            if (resultCode != Activity.RESULT_OK) {
                return;
            }
            String[] imageFiltersName = getResources().getStringArray(R.array.image_filters_name);
            Toast.makeText(getActivity().getApplicationContext(),
                    "requestCode : " + requestCode + ", resultCode : " + resultCode + " " +
                            getString(R.string.image_filter_selected_msg, imageFiltersName[data.getIntExtra(ImageFilterDialogFragment.SELECTED_FILTER_NAME, 0)]),
                    Toast.LENGTH_LONG).show();
            break;
    }
}
        

onActivityResultメソッド実行時に引数で渡したリクエストコードと結果コードを受け取り、トーストを表示します。

最後に、ダイアログ画面を表示するDialogFragmentを作成します。クラス名はImageFilterDialogFragmentにします。

{project_folder}/ImageFilterDialogFragment.java
public class ImageFilterDialogFragment extends DialogFragment {
    public static final String SELECTED_FILTER_NAME = "filter";
    private Fragment fragment;

    /**
     * コンストラクタ
     *
     * @return A new instance of fragment ImageFilterDialogFragment.
     */
    public static ImageFilterDialogFragment newInstance() {
        ImageFilterDialogFragment fragment = new ImageFilterDialogFragment();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        fragment = getTargetFragment();
        return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.image_filter_dialog_fragment_title)
                // 実装の解説1(jump)
                .setItems(R.array.image_filters_name, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // The 'which' argument contains the index position
                        // of the selected item
                        Intent intent = new Intent();
                        intent.putExtra(ImageFilterDialogFragment.SELECTED_FILTER_NAME, which);
                            // 実装の解説2(jump)
                        getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, intent);
                    }
                })
                .create();
    }
}

        

■ 実装の解説

1. setItems

AlertDialogの選択項目に配列のxmlを使用します。xmlは/res/values-ja/フォルダにarrays.xmlファイルの名前で作成します。

.setItems(R.array.image_filters_name, new DialogInterface.OnClickListener() {
        

xmlではresources属性内にstring-array属性を記述します。

<resources>
    <string-array name="image_filters_name">
        <item>グレースケール</item>
        <item>ガウシアンフィルタ</item>
    </string-array>
</resources>
        

getResources().getTextArrayが呼び出されて、ダイアログで表示できます。

fig5. ダイアログ

2. getTargetFragment

getTargetFragment()メソッドは、setTargetFragmentで設定したFragementを返します。このサンプルでは、ImageFragmentです。

getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, intent);
        

上記はImageFragmentのonActivityResultメソッドを任意に呼び出しています。本来onActivityResultメソッドはstartActivityForResultから呼び出されますが、ここではコールバックメソッドとして利用しています。

引数のリクエストコードは、setTargetFragmentで設定したリクエストコードをgetTargetRequestCodeメソッドで取得して設定しています。

ビルド

上記のコードビルドして動作を確認します。

fig6. sample app

ImageFilterDialogFragmentからImageFragmentにcallbackできました。

結論

FragmentからFragmentへのコールバックの方法はいくつかあります。しかし、この記事で説明した方法が一番安定した動作で、コードの可読性も良いと思います。FragmentからFragmentへのコールバックが必要な時はこの方法を利用してみてください。

タグ検索で調べてみよう

Android7.1