AndroidStudioで作成するfragmentの仕組みを簡単に知りたい人向け。

 AndroidStudioでFragmentを利用しようとして、IDEから作成すると以下のようなテンプレートが作成されます。これなんなの?と思う人も多いと思いますが、少し解説です。基本的にはコメントの英文を読むと理解できます。
 fragmentはアクティビティの部品の集合を使いまわすことができるUI部品のようなものです。プログラムでいうとincludeのようなもので、htmlでいうとinner htmlのような感じです。AndroidにもUIのレイアウトを使い回すincludeがありますが、includeと違うのはfragmentはプログラムを記述できることです。そのためプログラム部分を含めて再利用したり複数利用することができます。
 AndroidStudioでFragmentを生成すると以下のようなテンプレートがいきなり生成されるのでわけわからなくなることもあると思いますが、そのあたりを説明します。
 これがわかりにくいのは、複数の機能が実装されているからです。
  1. fragmentを実装する親のアクティビティを得る仕組み
  2. fragmentで発生したイベントを親のアクティビティへ伝える仕組み
  3. fragmentの生成時に処理を振り分ける仕組み
 これらの機能が複合的に絡み合っているので見た目的にわかりにくい状況になっています。

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
 * A simple [Fragment] subclass.
 * Activities that contain this fragment must implement the
 * [TestFragment.OnFragmentInteractionListener] interface
 * to handle interaction events.
 * Use the [TestFragment.newInstance] factory method to
 * create an instance of this fragment.
 *
 */
class TestFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null
    //呼び出しもとのアクティビティのインスタンスを格納する変数
    private var listener: OnFragmentInteractionListener? = null

    //パラメータによって異なる処理をする 
//アクティビティなどによってfragmentを少し変えたものを利用したいときなどに、パラメータを利用する
//最低でもOnCreateで変数に状態を保存しないとスマホ縦横時にライフサイクルの関係でメンバ変数は消える override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { param1 = it.getString(ARG_PARAM1) param2 = it.getString(ARG_PARAM2) } } //動的にデザインを適用する場合はここで適用する
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_test, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) } // TODO: Rename method, update argument and hook method into UI event //アクティビティにコールバックするメソッド
//イベントを発生させるUI部品ごとに適時メソッドを作成する fun onButtonPressed(uri: Uri) {
//アクティビティのメソッドを実行してコールバックする listener?.onFragmentInteraction(uri) } //親のアクティビティに紐付けられたときに発生するイベント //ここで親のアクティビティのインスタンスを得られるので保存しておく override fun onAttach(context: Context) { super.onAttach(context) if (context is OnFragmentInteractionListener) { listener = context } else { throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener") } } override fun onDetach() { super.onDetach() listener = null } /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated * to the activity and potentially other fragments contained in that * activity. * * * See the Android Training lesson [Communicating with Other Fragments] * (http://developer.android.com/training/basics/fragments/communicating.html) * for more information. */
//親のアクティビティで継承するインターフェースを宣言しておく //このメソッドをアクティビティに実装させて、こちらから呼ぶことでコールバックを実現する interface OnFragmentInteractionListener { // TODO: Update argument type and name fun onFragmentInteraction(uri: Uri) } companion object { /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment TestFragment. */ // TODO: Rename and change types and number of parameters @JvmStatic //fragmentのインスタンスを生成するメソッド //ここでインスタンスを生成して、パラメータを渡すことでfragmentを生成時に処理を変化させることができる //fragmentではコンストラクタに引数を渡せない、ライフサイクルの関係でメンバ変数が消える
//そのためBuilderパターンで引数を渡し、Bundleを利用してデータをやりとりする必要がある fun newInstance(param1: String, param2: String): Fragment = TestFragment().apply { arguments = Bundle().apply { putString(ARG_PARAM1, param1) putString(ARG_PARAM2, param2) } } } }

 それでは、人一つ順番にみてみましょう。

 fragmentを実装する親のアクティビティを得る仕組み
 
 まずはfragmentが実装されるアクティビティのインスタンスを得る部分です。この部分はonAttachから始まります。onAttachでアクティビティがアタッチされると発生するイベントです。上記のテンプレートでは、そのアクティビティのインスタンスがlistenerに保存されます。これによりアクティビティを操作できるようになります。
 このlistenerは呼び出し元のアクティビティそのものなので、キャストするとアクティビティのメソッドなども実行することができます。
 AndroidStudioが自動生成するコードのうち、親のアクティビティを取得したり、解放したりする処理の部分を書き出してみます。

//アクティビティのインスタンスを保存する変数
private var listener: OnFragmentInteractionListener? = null
//アクティビティのインスタンスの取得 override fun onAttach(context: Context) { super.onAttach(context) if (context is OnFragmentInteractionListener) { listener = context } else { throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener") } } override fun onDetach() { super.onDetach() listener = null }

 fragmentで発生したイベントを親のアクティビティへ伝える仕組み


 次にfragment側で発生してイベントをactivity側で受信する部分です。
 これは、interface OnFragmentInteractionListenerで実装しています。アクティビティ側ではOnFragmentInteractionListenerのインターフェースを継承するようにします。そしてイベントが発生したら、その関数が呼ばれるのでアクティビティ側でイベントを受けることができます。実際にイベントを発生させているのは、fun onButtonPressed(uri: Uri)ですが、それは任意に複数のメソッドを作成し、必要に応じてUI部品のイベントを受けます。
 それではイベントをアクティビティにコールバックするための処理の部分だけを抜き出してみます

//アクティビティのインスタンスを保存する変数
//アクティビティはOnFragmentInteractionListenerを継承している
private var listener: OnFragmentInteractionListener? = null
//アクティビティにコールバックさせる関数を実装させるためにインターフェースを作る
//この引数はUriにする必要なく自分で勝手に使用する引数に変更していい
//もちろんこれ以外の関数を追加しても構わない
interface OnFragmentInteractionListener { fun onFragmentInteraction(uri: Uri) }
//もしボタンが押されたときには、下記のようにアクティビティの関数を呼び出す
fun onButtonPressed(uri: Uri) { listener?.onFragmentInteraction(uri) }
 fragmentの生成時に処理を振り分ける仕組み

 最後はコンパニオンオブジェクトのfun newInstance(param1: String, param2: String)です。
 これは、動的にfragmentを実装するときにインスタンスを生成するのに利用します。fragmentはライフサイクルの関係から、このように生成したほうがいいようです。ここでパラメータを指定してあげることで、生成時に様々な設定をしてあげることができるようになります。
 それでは同様に生成時に処理を振り分ける部分のコードだけ抽出してみましょう。

//パラメータを格納する変数
private var param1: String? = null private var param2: String? = null
//コンストラクタで受け取ったパラメータを変数に格納する
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { param1 = it.getString(ARG_PARAM1) param2 = it.getString(ARG_PARAM2) } }

//bundleを利用してパラメータを渡す処理
//もちろん自分で好きなように変更して構わない
companion object { @JvmStatic fun newInstance(param1: String, param2: String) = TestFragment().apply { arguments = Bundle().apply { putString(ARG_PARAM1, param1) putString(ARG_PARAM2, param2) } } }

 まとめるとfragmentのテンプレートがやっていることは
  • アクティビティのインスタンスを保持して、アクティビティの関数やUI部品を操作できるようにする
  • fragment側で発生したイベントをアクティビティ側へ通知できるようにする
  • fragment生成時にパラメータを渡して必要に応じて処理や表示内容を変更できるようにする
この3つのことができるようになっているテンプレートということなになるようです。