Crash when using fragment in AndroidView using compose
Asked Answered
L

4

5

I'm playing around with compose and tried to include a fragment inside a compose AndroidView.

So in my situation, we have AFragment with a ComposeView and inside the ComposeView there is an AndroidView which create a FragmentContainerView and add a PIFragment.

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            setContent {
                AppTheme {
                    GalleryScreen(
                        factory = viewModelFactory,
                        remoteConfig = remoteConfig,
                        id = id,
                        currentPosition = currentPositionState,
                        onBack = { router.back(requireActivity()) },
                    ) {
                        AndroidView(
                            modifier = Modifier
                                .fillMaxWidth()
                                .wrapContentHeight(),
                            factory = {
                                FragmentContainerView(context).apply {
                                    id = R.id.pFIC
                                }
                            },
                            update = {
                                childFragmentManager.beginTransaction().replace(
                                    R.id.pFIC,
                                    PIFragment::class.java,
                                    buildArguments(
                                        id = id,
                                        origin = origin,
                                    ), null
                                ).commitAllowingStateLoss()
                            },
                        )
                    }
                }
            }
        }
    }

All was working fine, but when we publish this code in production, we saw crash in firebase:

    java.lang.IllegalArgumentException: No view found for id 0x7f0b072b (...:id/pFIC) for fragment PIFragment{ef1f89b} (bdbe15f0-679d-41bb-8a27-367655f73545 id=0x7f0b072b)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:513)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
        at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1647)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3128)
        at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:3065)
        at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:2988)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:546)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2180)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
        at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:524)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Linhliniment answered 29/11, 2021 at 9:0 Comment(0)
L
8

In fact, the crash was happening after the parent fragment was recreated.

In my case because of navigation:

  • AFragment (with PIFragment)
  • navigate to BFragment
  • back to AFragment

Crash, because the fragment manager is trying to recreate AFragment and PIFragment, but the compose pass is not done so pFIC does not exist yet.

The solution was to remove the PIFragment when the parent fragment view is destroyed.

    override fun onDestroyView() {
        childFragmentManager.findFragmentById(R.id.pFIC)?.let { fragment ->
            childFragmentManager.beginTransaction().remove(fragment).commitAllowingStateLoss()
        }
        super.onDestroyView()
    }
Linhliniment answered 29/11, 2021 at 9:0 Comment(0)
I
4

You should also be using AndroidViewBinding instead of AndroidView for adding a fragment in Compose. AndroidViewBinding has fragment-specific handling that will ensure that fragments are removed when the containing composable is disposed of. Note that this requires view binding to be enabled.

Define your FragmentContainerView in XML like so:

<androidx.fragment.app.FragmentContainerView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/fragment_container_view"
  android:layout_height="match_parent"
  android:layout_width="match_parent"
  android:name="com.example.MyFragment" />

...and then in your composable function:

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

You can read Fragment in Compose for more info.

Inscribe answered 24/6, 2022 at 21:8 Comment(5)
Is FragmentContainerView a separate layout file that resides next to the Fragment's original layout file?Immunogenetics
@Immunogenetics yes that would be a separate XML file. I've got a test branch here that demonstrates this in the Sunflower sample app which you can check out github.com/android/sunflower/tree/top-down-migrationInscribe
This actually works and also the documented way. Has to be the accepted answer. developer.android.com/jetpack/compose/migrate/…Panelboard
androidx.compose.ui:ui-viewbinding dependency needed.Telangiectasis
What if there are constructor parameters for the fragment? How can I pass it from compose?Lumumba
S
4

Since version 1.8.1 there is an AndroidFragment composable similar to the AndroidView composable, to do this kind of thing.

https://developer.android.com/jetpack/androidx/releases/fragment#1.8.0

Example:

AndroidFragment(clazz = PIFragment::class.java)

As simple as the above, and you have other parameters in there to pass whatever bundle arguments you need or if you need to do something when it updates through the FragmentManager.

Here you have a few more examples:

https://github.com/search?q=AndroidFragment++language%3AKotlin&type=code

Striptease answered 5/7 at 17:53 Comment(0)
L
1

I faced this exception too. I modified the sagix's answer to more convenient way for me. Check it out below. Don't forget use a childFragmentManager

@Composable
fun ReusableFragmentComponent(
    screen: SupportAppScreen,
    fragmentManager: FragmentManager,
    modifier: Modifier = Modifier
) {
    val containerId by rememberSaveable { mutableStateOf(View.generateViewId()) }

    AndroidView(
        modifier = modifier,
        factory = { context ->
            FragmentContainerView(ContextThemeWrapper(context, context.currentStyle())).apply {
                id = containerId
            }
        },
        update = {
            val fragmentAlreadyAdded = fragmentManager.findFragmentByTag(screen.screenKey) != null

            if (!fragmentAlreadyAdded) {
                fragmentManager
                    .beginTransaction()
                    .replace(containerId, screen.getFragment(), screen.screenKey)
                    .commit()
            }
        })

    DisposableEffect(LocalLifecycleOwner.current) {
        onDispose {
            fragmentManager.findFragmentById(containerId)?.let { fragment ->
                fragmentManager
                    .beginTransaction()
                    .remove(fragment)
                    .commitAllowingStateLoss()
            }
        }
    }
}
Lamas answered 3/3, 2023 at 16:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.