🔲

Jetpack ComposeのHorizontalPagerにFragmentを埋め込み、各Fragmentに引数を渡す方法

2023/03/24に公開

概要

Jetpack ComposeにFragmentを埋め込む方法です。
埋め込むFragmentに引数を渡す必要があったのと、HorizontalPagerに複数のFragmentを埋め込みたいという事情がありました。
公式docAndroidViewBindingを勧めているので、最初このようにしました。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/horizontal_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <androidx.fragment.app.FragmentContainerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/fragment_container_view"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:name="com.vitantonio.nagauzzi.PageFragment" />

</androidx.constraintlayout.widget.ConstraintLayout>
HorizontalPager(count = argList.size) { page ->
    AndroidViewBinding(RootFragmentBinding::inflate){
        val fragment = fragmentContainerView.getFragment<PageFragment>()
        fragment.arguments = argList[page]
    }
}

しかし、クロージャー内が呼ばれる前にFragmentがinflateされるのを避けられず、

java.lang.IllegalStateException: Fragment PageFragment{d4b9c8c} (00000000-0000-0000-0000-000000000000 id=0x00000000) has null arguments

となってしまいました。

色々調査して、AndroidViewBindingでは無理だと判断し、AndroidViewを使った方法で解決しました。

成功したコード

val fragments = argsList.map { args ->
    PageFragment().apply {
        arguments = args.bundle()
    }
}
HorizontalPager(count = argsList.size) { page ->
    AndroidView(factory = { context ->
        val frameLayout = FrameLayout(context).apply {
            id = R.id.container
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
        }.also {
            val transaction = supportFragmentManager.beginTransaction()
            transaction.replace(it.id, fragments[page])
            transaction.commit()
        }
        frameLayout
    }, update = {
        // レイアウト後に外から何かを設定したい場合、fragments[page]を使う
    })
}

補足

layoutParamsの設定は大事です。
今回HorizontalPager内のFragmentのViewが更にComposeを使っていて、その中のGlideが

compose java.lang.IllegalArgumentException: Failed requirement.

というエラーを出力し、原因がわからず悩んでいましたが、原因はlayoutParamsの設定忘れによる異常表示でした。

また、update内でit.findFragmentでFragmentを取得しても良いかなと思ったんですが、

java.lang.IllegalStateException: View android.widget.FrameLayout{0000000 V.E...... ......I. 0,0-0,0 #00000000 app:id/container} does not have a Fragment set

と出力されてうまくいかず、時間オーバーで探究を避けて上記の方法に落ち着きました。

Discussion