AndroidFragment.kt
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.fragment.compose
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.currentCompositeKeyHash
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.viewinterop.AndroidView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.commitNow
/**
* Allows for adding a [Fragment] directly into Compose. It creates a fragment of the given class
* and adds it to the fragment manager.
*
* Updating the [clazz] or [fragmentState] parameters will result in a new fragment instance being
* added to the fragment manager and invoke the [onUpdate] callback with the new instance.
*
* @sample androidx.fragment.compose.samples.BasicAndroidFragment
*
* @param modifier the modifier to be applied to the layout
* @param fragmentState the savedState of the fragment
* @param arguments args to be passed to the fragment
* @param onUpdate callback that provides the created fragment
*/
@Composable
inline fun <reified T : Fragment> AndroidFragment(
modifier: Modifier = Modifier,
fragmentState: FragmentState = rememberFragmentState(),
arguments: Bundle = Bundle.EMPTY,
noinline onUpdate: (T) -> Unit = { }
) {
AndroidFragment(clazz = T::class.java, modifier, fragmentState, arguments, onUpdate)
}
/**
* Allows for adding a [Fragment] directly into Compose. It creates a fragment of the given class
* and adds it to the fragment manager.
*
* Updating the [clazz] or [fragmentState] parameters will result in a new fragment instance being
* added to the fragment manager and invoke the [onUpdate] callback with the new instance.
*
* @sample androidx.fragment.compose.samples.BasicAndroidFragment
*
* @param clazz fragment class to be created
* @param modifier the modifier to be applied to the layout
* @param fragmentState the savedState of the fragment
* @param arguments args to be passed to the fragment
* @param onUpdate callback that provides the created fragment
*/
@Suppress("MissingJvmstatic")
@Composable
fun <T : Fragment> AndroidFragment(
clazz: Class<T>,
modifier: Modifier = Modifier,
fragmentState: FragmentState = rememberFragmentState(),
arguments: Bundle = Bundle.EMPTY,
onUpdate: (T) -> Unit = { }
) {
val updateCallback = rememberUpdatedState(onUpdate)
val hashKey = currentCompositeKeyHash
val view = LocalView.current
val fragmentManager = remember(view) {
FragmentManager.findFragmentManager(view)
}
val context = LocalContext.current
lateinit var container: FragmentContainerView
AndroidView({
container = FragmentContainerView(context)
container.id = hashKey
container
}, modifier)
DisposableEffect(fragmentManager, clazz, fragmentState) {
val fragment = fragmentManager.findFragmentById(container.id)
?: fragmentManager.fragmentFactory.instantiate(
context.classLoader, clazz.name
).apply {
setInitialSavedState(fragmentState.state.value)
setArguments(arguments)
fragmentManager.beginTransaction()
.setReorderingAllowed(true)
.add(container, this, "$hashKey")
.commitNow()
}
fragmentManager.onContainerAvailable(container)
@Suppress("UNCHECKED_CAST")
updateCallback.value(fragment as T)
onDispose {
val state = fragmentManager.saveFragmentInstanceState(fragment)
fragmentState.state.value = state
if (!fragmentManager.isStateSaved) {
// If the state isn't saved, that means that some state change
// has removed this Composable from the hierarchy
fragmentManager.commitNow {
remove(fragment)
}
}
}
}
}