Reputation: 171
How can I go from Jetpack compose screen to fragment and from fragment to jetpack compose screen? I use the jetpack compose navigation? any help?
Upvotes: 10
Views: 12946
Reputation: 26851
With Jetpack Fragment 1.8.0 recently made stable, the ability to use a Fragment
in a composable was added with the AndroidFragment
composable:
@Composable
fun ExampleFeatureScreen(...) {
AndroidFragment<ExampleFeatureFragment>()
// Or
AndroidFragment(clazz = ExampleFeatureFragment::class.java)
}
If you need to pass arguments to the Fragment
, you can use the arguments
parameter which accepts a Bundle
:
class ExampleFeatureFragmentWithArgs : Fragment(...) {
override fun onViewCreated(...) {
val exampleId = requireArguments().getString(KEY_EXAMPLE_ID)
}
companion object {
const val KEY_EXAMPLE_ID = "example-id"
}
}
@Composable
fun ExampleFeatureScreenWithArgs(exampleId: String) {
val args = bundleOf(ExampleFeatureFragmentWithArgs.KEY_EXAMPLE_ID to exampleId)
AndroidFragment<ExampleFeatureFragmentWithArgs>(arguments = args)
// Or
AndroidFragment(clazz = ExampleFeatureFragmentWithArgs::class.java, arguments = args)
}
Internally, AndroidFragment
just uses a FragmentContainerView
similar to what you would be using from Daniele's answer:
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) }
}
}
}
}
Upvotes: 1
Reputation: 12909
This is the recommended way of doing it per Google.
Enable view binding
Make sure your Activity is an AppCompatActivity
Create a layout for the fragment container:
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.compose.snippets.interop.MyFragment" />
make sure you have declared the compose viewbinding dependency androidx.compose.ui:ui-viewbinding
Create the Composable containing the Fragment:
@Composable
fun FragmentInComposeExample() {
AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
val myFragment = fragmentContainerView.getFragment<MyFragment>()
// ...
}
}
Upvotes: 6
Reputation: 6863
You can use AndroidView() and ComposeView() for this
Upvotes: -2
Reputation: 29360
If you have a hybrid app with both compose and view system fragments then the recommendation is to host all your composable screens in fragments and use the fragment navigation. When you are ready to go all in with compose you can then switch to compose navigation.
See this for reference.
Upvotes: 24