公開日: 2024/06/15
androidx.fragment:fragment-*:1.8.0
がリリースされ、AndroidFragment
composableがstableバージョンで使用できるようになりました。
以前までは、Fragments in Composeを実現するために、AndroidViewBinding
を使うことが推奨されていました。
しかし、androidx.fragment
の1.8.0
のリリースのノート[1]に以下のような記述がある通り、今後はAndroidFragment
がAndroidViewBinding
の代替となるようです。
This should be used as a direct replacement for the previously recommended approach of using AndroidViewBinding to inflate a Fragment.
以下のポストにある通り、AndroidFragment
はAndroidViewBinding
を使用する方法で解決できなかった多くの問題点を解決してくれているとのことです。
https://x.com/ianhlake/status/1775710793897525619
本記事では、Fragments in Composeの新しい実装方法となったAndroidFragment
の使用方法とAndroidFragment
の内部実装について解説します。
AndroidFragment
の使用方法例えば、以下のようなFragmentとレイアウトファイルがあるとします。
...
class SampleFragment : Fragment(R.layout.fragment_sample) {
lateinit var binding: FragmentSampleBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSampleBinding.inflate(layoutInflater)
return binding.root
}
}
<?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">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
AndroidFragment
を使用して以下のように記述するだけで、ComposeからFragmentを使用することができます。
@Composable
fun HogeScreen() {
AndroidFragment<SampleFragment>(
onUpdate = { fragment ->
fragment.binding.textView.text = "Fragment in Compose!!"
}
)
...
}
実行すると以下のように、TextViewに「Fragment in Compose!!」という文字列が表示されていることがわかると思います。
実行結果
AndroidFragment
の内部実装AndroidFragment
の内部実装を解説していきます。
AndroidFragment
のパラメータについてAndroidFragment
は以下のように定義されています。
@Composable
<T : Fragment> AndroidFragment(
clazz: Class<T>,
modifier: Modifier,
fragmentState: FragmentState,
arguments: Bundle,
onUpdate: (T) -> Unit
)
それぞれのパラメータは以下のような役割になります。
clazz
: 作成したいFragmentのクラスmodifier
: レイアウトに適用するModifierfragmentState
: Fragmentの状態保持用のState(後述)arguments
: Fragmentに渡すarguments(これあるのだいぶ嬉しい)onUpdate
: 作成されたFragmentのインスタンスを提供するコールバックAndroidFragment
の内部実装を見ると以下のコードがあります。
DisposableEffect(...) {
val fragment = fragmentManager.findFragmentById(container.id)
?: fragmentManager.fragmentFactory.instantiate(
context.classLoader, clazz.name
)
...
}
すでにFragmentがfragmentManagerに追加されていればそのインスタンスを取得し、追加されていなければ、Fragmentのクラス名(=clazz.name
)を指定して新たにFragmentのインスタンスを作成しています。
以下のようにFragmentContainerView
をAndroidView
に渡すことで表示しています。
(ここはAndridViewBinding
を使用した方法を踏襲していそうですね!)
FragmentContainerView
は、指定された場所にフラグメントを表示するためのコンテナであり、ここに作成するFragmentのインスタンスをaddしています。
lateinit var container: FragmentContainerView
AndroidView({
container = FragmentContainerView(context)
container.id = hashKey
container
}, modifier)
...
DisposableEffect(...) {
val fragment = fragmentManager.findFragmentById(container.id)
?: fragmentManager.fragmentFactory.instantiate(
context.classLoader, clazz.name
).apply {
...
fragmentManager.beginTransaction()
.setReorderingAllowed(true)
.add(container, this, "$hashKey")
.commitNow()
}
...
}
Configuration Changedやプロセスのキルが発生したときのために、AndroidFragment
はfragmentState
をパラメータとして受け取ります。これはFragmentの状態を保存するために使用されます。
デフォルトではパラメータとしてrememberFragmentState()
を使用しており、以下のように定義されています。
@Composable
fun rememberFragmentState(): FragmentState {
return rememberSaveable(saver = fragmentStateSaver()) {
FragmentState()
}
}
@Stable
class FragmentState(
internal var state: MutableState<Fragment.SavedState?> = mutableStateOf(null)
)
private fun fragmentStateSaver(): Saver<FragmentState, *> = Saver(
save = { it.state },
restore = { FragmentState(it) }
)
rememberSaveable
を使用することで、Acitivityの破棄に耐えられるようにFragmentStateを保持しています。FragmentStateは、Fragmentの状態情報を管理するクラスであるFragment.SavedState?
のMutableStateを保持しています。
さらに、fragmentState
はAndroidFragment
内で以下のように使用されています。
DisposableEffect(...) {
val fragment = fragmentManager.findFragmentById(container.id)
?: fragmentManager.fragmentFactory.instantiate(
context.classLoader, clazz.name
).apply {
setInitialSavedState(fragmentState.state.value)
...
}
onDispose {
val state = fragmentManager.saveFragmentInstanceState(fragment)
fragmentState.state.value = state
...
}
}
fragmentのインスタンス取得後、setInitialSavedState()
を使用して、Fragment.SavedState?
の初期値をセットしています。これがFragmentの状態の復元処理になります。
そして、AndroidFragment
がCompositionから退場したときに、fragmentManager経由でFragment.SavedState?
をfragmentStateに保存しています。
AndroidFragment
について解説しました。
まだまだFragmentが残っているプロダクトが多いのが現状だと思うので、うまくAndroidFragment
を使用してCompoe化を進めていきましょう!!
https://developer.android.com/jetpack/androidx/releases/fragment
https://developer.android.com/reference/kotlin/androidx/fragment/compose/package-summary