We’re converting a Fragment based app over to Comp...
# compose
b
We’re converting a Fragment based app over to Compose. It’s got quite a few Fragments. During this transition period, I know I can use the
AndroidViewBinding
Composable to inflate a
FragmentContainerView
with a Fragment and show it within composable “screen”. So far though, it seems like I have to create a wrapper xml layout resource with a
FragmentContainerView
for every fragment. I was hoping to find a way to just define a single xml layout resource (compose_fragment_container.xml) and write a composable that uses it with
AndroidViewBinding
to load an arbitrary Fragment. But so far no luck. Anyone know a way to do this? My current (not working) code in the 🧵
This is what I’m using now: fragment_compose_container.xml:
Copy code
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView
    xmlns:android="<http://schemas.android.com/apk/res/android>"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/compose_fragment" />
A Composable to wrap
AndroidViewBinding
, inflate the above layout, and add a
Fragment
to it:
Copy code
@Composable
fun MyFragmentBinding(fragment: Class<out Fragment>, fragmentArgs: Bundle? = null) {
    val activity = (LocalContext.current as? FragmentActivity)

    AndroidViewBinding(factory = { inflater, parent, attachToParent ->
        // Inflate the fragment container view
        val view = FragmentComposeContainerBinding.inflate(inflater, parent, attachToParent)

        val fm = activity?.supportFragmentManager ?: throw IllegalStateException("MyFragmentBinding composable can only be used in a FragmentActivity. Cannot inflate $fragment.")

        // Add the fragment to the container view
        fm.commit {
            setReorderingAllowed(true)
            add(R.id.compose_fragment, fragment, fragmentArgs)
        }
        view
    }) 
}
This works the first time I call it, and loads a fragment just fine. When I call it again though (say when the user navigates to another screen), I end up with a blank screen.
l
I don't fully understand what you are trying to achieve but in my project the activity inflates the
androidx.fragment.app.FragmentContainerView
and I use the following block in each of my Fragment's
onCreateView
method.
Copy code
return ComposeView(requireContext()).apply {
            setContent {
              //MaterialTheme() composable here 
            }
   }
I don't have xml resource for any fragments but I use AndroidX nav components so I just have to define all the fragments in XML nav graph.
b
My Fragments have no compose at all and work fine. I’m trying to load one of many fragments in my compose-based activity. An extremely basic example might look something like this:
Copy code
class MyActivity(): FragmentActivity {

 private val viewModel: MyActivityViewMmodel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        setContent {
            when (viewModel.someState) {
               screen1 -> MyFragmentBinding(Screen1Fragment::class.java)
               screen2 -> MyFragmentBinding(Screen3Fragment::class.java)               
               screen3 -> MyFragmentBinding(Screen3Fragment::class.java)        
       }
    }
}
I’m using the Navigation component, so it’s more like this:
Copy code
class MyActivity(): FragmentActivity {

 private val viewModel: MyActivityViewMmodel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        setContent {
           val navController = rememberNavController()
            NavHost(navController = navController, startDestination = "home") {
                 composable(route="home") {
                       MyFragmentBinding(Screen1Fragment::class.java)
                 }
                 composable(route="screen2") {
                       MyFragmentBinding(Screen2Fragment::class.java)
                 }
                 composable(route="screen3") {
                       MyFragmentBinding(Screen3Fragment::class.java)
                 }
    }
}
The “home” screen (Screen1Fragment) loads up just fine. but from there if I call
navController.navigate("screen2")
… I get a blank screen.
m
you could do that but it is harder to migrate that way. In my opinion what you should do is: 1) Keep the navigation with fragments the same. Start refactoring fragments to use compose inside (onCreateView + ComposeView). After all fragments migrated that way, then its much easier to completely get rid of frags and just use 100% compose
b
@myanmarking - I agree. I’m exploring this as a a possibility, not something we’ll definitely implement. We’re about half migrated so far, and now our product team wants to introduce a bottom nav bar to the app. Since it’s so closely tied to navigation, I am exploring the possibility of migrating the core architecture and navigation over to compose and then circling back to remove fragments as their contents get converted to compose. I know it’s not the recommended approach, but I’m curious as to what kind of hurdles we’d face so I’m trying it out.
c
Hey @Bradleycorn I also face similar issue when trying to switch between legacy
User Logged In
and
Guest
page. Do you have any update on this ?
b
No. We ended up not doing it.
c
FYI: for someone who got similar issue: • We found that blank screen because of fragment view got onDispose immediately (In my case I guest because of state change -> UI got recompose -> but UI still invisible to user -> that why it got dispose) Available options: • Option 1: Delay update state when UI is visible to user to trigger re-composition • Option 2: ◦ Nested Guest/User Logged view inside
legacy fragment
◦ Put legacy fragment inside
FragmentContainerView
xml ◦ Using
AndroidViewBinding
to load xml ◦ Inside
legacy fragment
add logic to switching Guest/User Logged base on state
158 Views