ActivityとFragmentの連携を理解する【Android・Kotlin】

この記事はActivityとFragmentの連携の理解を深めるための忘備録である。図のように、2つの Fragment を1つの Activity で操作する実験を行ってみた。できる限り最小限で簡単なサンプルプロジェクトにしたつもりだ。以下プログラムの詳細を説明していく。


画像の拡大

activity_main.xmlを用意する

まずFragmentを動的に追加するためのコンテナとして、空っぽの状態のレイアウトファイルを用意する。今回は1つだけしかActivityを使わないのでactivity_main.xmlを修正することにした。ConstraintLayoutidcontainerとしていることに注意しよう。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity" android:id="@+id/container">
</android.support.constraint.ConstraintLayout>

ActivityからFragmentを呼び出す

ActivityonCreateで、最初に表示したいFragmentを設定するには次のコードを書く。

override fun onCreate(savedInstanceState: Bundle?) {
	super.onCreate(savedInstanceState)
	setContentView(R.layout.activity_main)

	val hogeFragment = HogeFragment()
	val transaction = supportFragmentManager.beginTransaction()
	transaction.add(R.id.container, hogeFragment)
	transaction.commit()
}

ここではHogeFragmentクラスを用意することにした。HogeFragmentクラスの内容は次のとおり。

class HogeFragment : Fragment() {

    private var listener: OnHogeFragmentListener? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val fragmentView = inflater.inflate(R.layout.fragment_hoge, container, false)
        val addFragment = fragmentView.findViewById<Button>(R.id.addFragment)
        addFragment.setOnClickListener {
            addFragment()
        }
        return fragmentView
    }

    fun addFragment() {
        listener?.onHogeFragmentAddFragment()
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnHogeFragmentListener) {
            listener = context
        } else {
            throw RuntimeException("$context must implement OnHogeFragmentListener")
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    interface OnHogeFragmentListener {
        fun onHogeFragmentAddFragment()
    }

}

HogeFragmentクラスでは、レイアウトファイルのonClick属性にメソッドを実装したかったのだが、実際にボタンを押すと次のエラーが出てしまった。

java.lang.IllegalStateException: Could not find method onButtonPressed(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.v7.widget.AppCompatButton with id 'button'
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.resolveMethod(AppCompatViewInflater.java:424)
    

HogeFragmentクラス内のメソッドを見つけてくれないようだ。仕方がないのでsetOnClickListenerでクラスへ直接書き込むことにした。

override fun onCreateView(
	inflater: LayoutInflater, container: ViewGroup?,
	savedInstanceState: Bundle?
): View? {
	val fragmentView = inflater.inflate(R.layout.fragment_hoge, container, false)

	val addFragment = fragmentView.findViewById<Button>(R.id.addFragment)
	addFragment.setOnClickListener {
		addFragment()
	}
	return fragmentView
}

inflaterで生成したViewからfindViewByIdでボタンの参照を取得する。そしてsetOnClickListenerでボタンのクリックイベントを設定する。

Fragment内のViewにアクセスしたい

ところでFragment内のTextViewにアクセスしたい場合はどうすればよいだろうか?

最良の方法かどうかはわからないが、次のようにしてViewの参照を保持することで可能になった。考え方はさきほどのボタンイベントの設定のときと同じだ。

private var fugaText:TextView? = null

override fun onCreateView(
	inflater: LayoutInflater, container: ViewGroup?,
	savedInstanceState: Bundle?
): View? {
	val view = inflater.inflate(R.layout.fragment_fuga, container, false)
	fugaText = view.findViewById(R.id.fugaText)
	...
}

このTextViewを実装したのが次のFugaFragmentクラスである。

private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

class FugaFragment : Fragment() {
    private var param1: String? = null
    private var param2: String? = null
    private var listener: OnFugaFragmentListener? = null
    private var fugaText:TextView? = null

    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? {
        val view = inflater.inflate(R.layout.fragment_fuga, container, false)

        fugaText = view.findViewById(R.id.fugaText)


        val displayParams = view.findViewById<Button>(R.id.displayParams)
        displayParams.setOnClickListener {
            displayParams()
        }

        val finishButton = view.findViewById<Button>(R.id.finishButton)
        finishButton.setOnClickListener {
            finish()
        }
        return view
    }

    fun displayParams() {
        fugaText?.text = "$ARG_PARAM1: $param1 / $ARG_PARAM2: $param2"
    }

    fun finish() {
        listener?.onHugaFragmentFinish()
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnFugaFragmentListener) {
            listener = context
        } else {
            throw RuntimeException("$context must implement OnFugaFragmentListener")
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
        println("FugaFragment onDetach")
    }


    interface OnFugaFragmentListener {
        fun onHugaFragmentFinish()
    }

    companion object {
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            FugaFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }
}

ActivityからFragmentへ値を渡す


画像の拡大

このFugaFragmentにはcompanion objectのシングルトンの宣言がある。Android StudioFragmentを新規作成するときに、include fragment factory methods?にチェックを入れることでファクトリーメソッドが自動生成されるのだ。

private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

class FugaFragment : 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)
        }
    }

	...
	companion object {
		@JvmStatic
		fun newInstance(param1: String, param2: String) =
			FugaFragment().apply {
				arguments = Bundle().apply {
					putString(ARG_PARAM1, param1)
					putString(ARG_PARAM2, param2)
				}
			}
	}
}

ファクトリーメソッドを使ってActivityからFragmentを生成してみよう。

val hugaFragment = FugaFragment.newInstance("りんご", "バナナ")

ファクトリーメソッドを使うことでFragmentActivityからパラメータを受けてることができる。

Fragmentの切り替えとバックスタックへ追加


画像の拡大

FugeFragmentのボタンを押すとActivityonFugeFragmentAddFragment通知が届くようになっている。onFugeFragmentAddFragmentメソッドの中でFragmentFugaFragmentへ切り替えてみる。

override fun onHogeFragmentAddFragment() {
	val hugaFragment = FugaFragment.newInstance("りんご", "バナナ")
	val transaction = supportFragmentManager.beginTransaction()
	transaction.replace(R.id.container, hugaFragment)
	transaction.addToBackStack(null) // バックスタックに保存する。呼び出さなければ積まれない。
	transaction.commit()
}

transaction.addToBackStack(null)の一行に注目しよう。この処理を書くことで、1つ前のHogeFragmentはバックスタックへ積まれることになる。バックスタックに積んだことによって、FugaFragmentから1つ前のHogeFragmentへ戻ることが可能となるのだ。また端末の戻るボタンからも1つ前のFragmentへ戻ることができる。もしも戻ることを許可したくない場合はaddToBackStackの一行を書かなければ良い。

ところでActivityFragmentのやり取りはインタフェースを介してやりとりしているが、以下の記事でも説明してたのでここでは省略する。

1つ前のFragmentへ戻る


画像の拡大

戻るときは簡単で、積んであるBackStackから(Popする)取り出せばよい。

override fun onHugaFragmentFinish() {
	supportFragmentManager.popBackStack()
}

BackStackからPopすると現在の表示されているFragmentonDetachが呼ばれ開放される。

まとめ

FragmentActivityに似たライフサイクルをもつ事ができるクラスだ。Viewを持たないFragmentも作ることができる。これはFragment単位でさまざまな処理を分担できることを意味しており、Activityからの制御を行いやすくすることができる。したがって重要な大まかな流れだけをActivityに書くことで、格段にメンテナンスがしやすくなるのだ。

今回のサンプルもまたGitHubへプッシュしておいたので参考に。

デスクワークの負担を減らすアイテム

作業効率をアップさせるには姿勢が大事です!実際に使ってみてどれもオススメなのでよかったら参考に!

Bestandノートパソコンスタンド 11 '' -16 '' Macbook Air Pro/富士通と互換性のある放熱性に優れたアルミニウム合金PCスタンド-シルバー
Bestandノートパソコンスタンド 11 '' -16 '' Macbook Air Pro/富士通と互換性のある放熱性に優れたアルミニウム合金PCスタンド-シルバー

ノートパソコンの高さをあげると、長時間のデスクワークでも作業者の負担を軽減する事が可能です。 アルミニウム製品、純正品、高品質、高級感があ ります。 対応機種:MacBook、MacBook Air、MacBook Pro、富士通と対応11"-16"ノートパソコン スタンド.

Amazon
AKRacing ゲーミングチェア Pro-X V2 GREY
AKRacing ゲーミングチェア Pro-X V2 GREY

AKRacingハイエンドゲーミングチェア。大きめの座面でゆったりとした座り心地。張地を経年劣化に強い高耐久仕様のPUレザーに変更したアップグレードモデル 寸法:背もたれ幅55cm/奥行55cm/高さ95cm、座面下高さ:32~39cm、座面厚さ:13cm、重量:25kg

Amazon
Kensington ExpertMouse ワイヤレストラックボール K72359JP
Kensington ExpertMouse ワイヤレストラックボール K72359JP

接続方式/Bluetooth、2.4GHz USB付属品/レシーバー、パームレスト、日本語取扱説明書 サイズ(本体):W130×D157×H65mm 対応OS:2.4GHzUSB:Windows10/8.1/8/7、MacOS10.8以降/Bluetooth4.0LE:Windows10/8.1、MacOS10.8以降

AmazonRakuten

Anrdoidアプリ開発にオススメの書籍

こちらの本は、図や絵が多くてわかりやすく、Androidアプリ開発初心者でも理解しやすい内容。基本的なことはしっかりおさえられるので、この手の本は一冊読んでおくと良と思う。

基本からしっかり身につくAndroidアプリ開発入門
基本からしっかり身につくAndroidアプリ開発入門

圧倒的な多数のユーザーが使っているヤフーのアプリ。その制作の最前線にいる黒帯エンジニアが、ユーザーが使いやすいアプリの大切な基本をしっかりと解説します。

KindleAmazonRakuten

Kotlinの実践的な使い方が学べる内容の本で、プログラミング技術をもっと磨きたい人向け。センスの良いプログラミングがたくさん紹介されていて、個人的には「目からうろこ」の連続だった本。

Kotlinプログラミング
Kotlinプログラミング

Kotlinは、Javaとの相互運用を可能にし、Android OSでGoogleがフルサポートする静的型プログラミング言語です。この言語は、Javaだけでは十全ではない(Javaだけでは実装に手間がかかりすぎる)、軽量かつ豊かな表現形式や、他言語ではすでに実装されている最新の機能を盛り込んでいます。

KindleAmazonRakuten

参考

Amazonでお得に購入するなら、Amazonギフト券がオススメ!

\Amazonギフトがお得/

コンビニ・ATM・ネットバンキングで¥5,000以上チャージすると、プライム会員は最大2.5%ポイント、通常会員は最大2%ポイントがもらえます!
Amazonギフト券

\この記事をシェアする/