Mustafa Abdullah
Mustafa Abdullah

Reputation: 171

Navigate from Jetpack Compose to Fragment and from Fragment to Jetpack Compose Screen

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

Answers (4)

Edric
Edric

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

Daniele Segato
Daniele Segato

Reputation: 12909

This is the recommended way of doing it per Google.

  1. Enable view binding

  2. Make sure your Activity is an AppCompatActivity

  3. 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" />
    
  4. make sure you have declared the compose viewbinding dependency androidx.compose.ui:ui-viewbinding

  5. Create the Composable containing the Fragment:

     @Composable
     fun FragmentInComposeExample() {
         AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
             val myFragment = fragmentContainerView.getFragment<MyFragment>()
             // ...
         }
     }
    

Upvotes: 6

Richard Onslow Roper
Richard Onslow Roper

Reputation: 6863

You can use AndroidView() and ComposeView() for this

https://youtu.be/PjQdFmiDgwk

Upvotes: -2

Francesc
Francesc

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

Related Questions