为什么我在使用 FragmentContainerView 和 AndroidViewBinding 时收到“嵌套片段应始终使用子 FragmentManager”

Ann*_*son 5 android android-fragments android-jetpack-compose

致命异常:java.lang.IllegalStateException 拥有视图 androidx.fragment.app.FragmentContainerView{43d1939 VE 的片段 SwlmFragment{6eaed00} (85ccadae-8789-4527-8c0f-9dfb23db0388 id=0x7f0a065e) ...... .... 0,0-1080,1978 #7f0a0926 app:id/locked} 已被销毁。嵌套片段应始终使用子 FragmentManager。

^ 这是我遇到的崩溃。

这是我正在使用的代码:

    @Composable
    fun SetupNavGraph(
        modifier: Modifier,
        navController: NavHostController,
        startDestination: String
    ) {
        NavHost(
            navController = navController,
            modifier = modifier,
            startDestination = startDestination
        ) {
            composable(
                route = Destination.premium
            ) {
                AndroidViewBinding(ComposeFragmentPremiumBinding::inflate) {
                    premiumFragment = this.premium.getFragment()
                }
            }
            composable(
                route = Destination.locked
            ) {
                AndroidViewBinding(ComposeFragmentLockedBinding::inflate) {
                    lockedFragment = this.locked.getFragment()
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

它崩溃了lockedFragment = this.locked.getFragment()

SetupNavGraph 是从父片段 swlmFragment 调用的,该片段位于普通 Activity (MainActivity) 内。使用 mobile_navigation.xml 文件和 NavController 将父片段导航到正常活动内部

这是使用 FragmentContainerView 的 xml 代码:

<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/locked"
    android:name="com.example.myApp.locked.LockedFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
</layout>
Run Code Online (Sandbox Code Playgroud)

这就是 AndroidViewBinding 正在做的事情(这是一个内置的 compose 函数,可以让您膨胀片段):

@Composable
fun <T : ViewBinding> AndroidViewBinding(
    factory: (inflater: LayoutInflater, parent: ViewGroup, attachToParent: Boolean) -> T,
    modifier: Modifier = Modifier,
    update: T.() -> Unit = {}
) {
    val viewBindingRef = remember { Ref<T>() }
    val localView = LocalView.current
    // Find the parent fragment, if one exists. This will let us ensure that
    // fragments inflated via a FragmentContainerView are properly nested
    // (which, in turn, allows the fragments to properly save/restore their state)
    val parentFragment = remember(localView) {
        try {
            localView.findFragment<Fragment>()
        } catch (e: IllegalStateException) {
            // findFragment throws if no parent fragment is found
            null
        }
    }
    val fragmentContainerViews = remember { mutableStateListOf<FragmentContainerView>() }
    val viewBlock: (Context) -> View = remember(localView) {
        { context ->
            // Inflated fragments are automatically nested properly when
            // using the parent fragment's LayoutInflater
            val inflater = parentFragment?.layoutInflater ?: LayoutInflater.from(context)
            val viewBinding = factory(inflater, FrameLayout(context), false)
            viewBindingRef.value = viewBinding
            // Find all FragmentContainerView instances in the newly inflated layout
            fragmentContainerViews.clear()
            val rootGroup = viewBinding.root as? ViewGroup
            if (rootGroup != null) {
                findFragmentContainerViews(rootGroup, fragmentContainerViews)
            }
            viewBinding.root
        }
    }
    AndroidView(
        factory = viewBlock,
        modifier = modifier,
        update = { viewBindingRef.value?.update() }
    )

    // Set up a DisposableEffect for each FragmentContainerView that will
    // clean up inflated fragments when the AndroidViewBinding is disposed
    val localContext = LocalContext.current
    fragmentContainerViews.fastForEach { container ->
        DisposableEffect(localContext, container) {
            // Find the right FragmentManager
            val fragmentManager = parentFragment?.childFragmentManager
                ?: (localContext as? FragmentActivity)?.supportFragmentManager
            // Now find the fragment inflated via the FragmentContainerView
            val existingFragment = fragmentManager?.findFragmentById(container.id)
            onDispose {
                if (existingFragment != null && !fragmentManager.isStateSaved) {
                    // If the state isn't saved, that means that some state change
                    // has removed this Composable from the hierarchy
                    fragmentManager.commit {
                        remove(existingFragment)
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是 swlmFragment:

class SwlmFragment : Fragment(), SWLMPresenter.View,
    SWLMMoreOptionsInteractor {

    @Inject
    lateinit var presenter: SWLMPresenter

    @Inject
    lateinit var pref: SharedPreferences

    private val SWLMViewModel: SWLMViewModel by activityViewModels()
    private val SWLMPremiumViewModel: SWLMPremiumViewModel by activityViewModels()
   

    private lateinit var navController: NavController
    private lateinit var composeNavController: NavHostController

    private lateinit var premiumFragment: SeeWhoLikesMePremiumFragment
    private lateinit var premiumGridFragment: SeeWhoLikesMePremiumGridFragment
    private lateinit var lockedFragment: SeeWhoLikesMeLockedFragment
    private lateinit var lockedGridFragment: SeeWhoLikesMeLockedGridFragment

    private var swlmMoreOptionsDialogFragment: SWLMMoreOptionsDialogFragment? = null

    private var startDestination: String = Destination.locked


    // Grid AB Test
    private val remoteConfigShowGrid = DynamicConfig.getBoolean(DynamicConfig.IS_SWLM_GRID_ENABLED)
    private var gridBaselineEnabled: Boolean = false
    private var gridVariantEnabled: Boolean = false
    private var showGrid: Boolean = false

    @ExperimentalAnimationApi
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        AndroidSupportInjection.inject(this)
        requireActivity().setLightStatusBar()
        Analytics.track(C.analytics.swlmScreenViewed, AnalyticsTrackerSelector.FIREBASE)

        navController = findNavController()

        // Grid AB Test
        gridBaselineEnabled = pref.getBoolean(C.pref.enableGridBaseLine, false)
        gridVariantEnabled = pref.getBoolean(C.pref.enableGridVariant, false)
        showGrid = !gridBaselineEnabled && (remoteConfigShowGrid || gridVariantEnabled)


        val hasPremium = swlmViewModel.premiumEnabled.value?.value

        startDestination = if (hasPremium == true) {
            if (showGrid) {
                Destination.premiumGrid
            } else {
                Destination.premium
            }
        } else {
            if (showGrid) {
                Destination.lockedGrid
            } else {
                Destination.locked
            }
        }

        return ComposeView(requireContext()).apply {
            setContent {
                Preview()
            }
        }
    }

    @ExperimentalAnimationApi
    @Composable
    private fun Preview() {
        AppPreview {
            Surface(
                modifier = Modifier.fillMaxSize(),
                color = MaterialTheme.colors.background
            ) {
                Column(modifier = Modifier.fillMaxSize()) {
                    var disableSwlm by remember {
                        mutableStateOf(false)
                    }

                    swlmViewModel.isSuspended.value?.let { isSuspended ->
                        disableSwlm = isSuspended
                    }

                    if (disableSwlm) {
                        Column(
                            modifier = Modifier
                                .fillMaxSize()
                                .background(Color.Black)
                        ) {}
                    } else {

                        composeNavController = rememberNavController()
                        Box {
                            SetupNavGraph(
                                modifier = Modifier
                                    .padding(
                                        top = if (showGrid) {
                                            if (youLikedEnabled) {
                                                90.dp
                                            } else {
                                                75.dp
                                            }
                                        } else {
                                            0.dp
                                        }
                                    )
                                    .fillMaxHeight()
                                    .fillMaxWidth(),
                                navController = composeNavController,
                                startDestination = startDestination
                            )

                            val massDeleteSelected by swlmViewModel.massDeleteSelected.observeAsState()
                            val premiumUndoEnabled by swlmPremiumViewModel.undoEnabled.observeAsState()
                            val hasPremium by swlmViewModel.premiumEnabled.observeAsState()

                            if (showGrid) {
                                if (hasPremium?.value == true) {
                                    // Premium Nav Bar
                                    NavBar(item = NavBarItem(type = NavBarType.SWLM_PREMIUM,
                                        onCLick = {
                                            
                                            }

                                            swlmMoreOptionsDialogFragment =
                                                swlmMoreOptionsDialogFragment.newInstance()
                                            swlmMoreOptionsDialogFragment?.canUndo =
                                                if (::premiumGridFragment.isInitialized) premiumGridFragment.presenter.state.canUndo else false
                                            swlmMoreOptionsDialogFragment?.shouldHaveSelect = true
                                            val selectIsEnabled =
                                                
                                                    swlmMePremiumViewModel.outOfProfilesIsVisible.value?.value != true
                                                
                                            swlmMoreOptionsDialogFragment?.selectIsEnabled =
                                                selectIsEnabled
                                        
                                            swlmMoreOptionsDialogFragment?.interactor =
                                                this@SwlmFragment
                                            swlmMoreOptionsDialogFragment?.show(
                                                childFragmentManager,
                                                "swlm_more_options"
                                            )
                                        },
                                        extender = SWLMExtender(
                                            viewModel = swlmViewModel,
                                            massDownSwipeSelected = massDeleteSelected?.value == true,
                           onDone = {
                                                swlmViewModel.addMassDeleteSelected(
                                                    false
                                                )
                                            },
                                            onUndo = {
                                                // undo from header
                                
                                                    if (::premiumGridFragment.isInitialized) {
                                                        premiumGridFragment.undoLastAction()
                                                    }
                                                }
                                            }
                                        )
                                    ))
                                } else {
                                    // Free NavBar
                                    NavBar(item = NavBarItem(onCLick = {
                                       
                                            lockedGridFragment.openSWLMPaywall()
                                        }
                            
    }

    @ExperimentalAnimationApi
    @Preview(showBackground = true)
    @Composable
    private fun ToolsPreview() {
        AppPreview(content = {
            Column(modifier = Modifier.fillMaxSize()) {
                NavBar(item = NavBarItem(type = NavBarType.SWLM_PREMIUM,
                    onCLick = {
                        swlmMoreOptionsDialogFragment = swlmMoreOptionsDialogFragment.newInstance()
                        swlmMoreOptionsDialogFragment?.canUndo = premiumGridFragment.presenter.state.canUndo
                        swlmMoreOptionsDialogFragment?.shouldHaveSelect = true
                        swlmMoreOptionsDialogFragment?.interactor = this@SWLMFragment
                        swlmMoreOptionsDialogFragment?.show(childFragmentManager, "swlm_more_options")
                    },
                    extender = SWLMExtender(
                        massDownSwipeSelected = true,
                        undoEnabled =  true,
                        onDone = {
                            SWLMViewModel.addMassDeleteSelected(false)
                        },
                        onUndo = {
                            premiumGridFragment.undoLastAction()
                        }
                    )
                ))

                val navController = rememberAnimatedNavController()
                SetupNavGraph(modifier = Modifier
                    .fillMaxWidth()
                    .fillMaxHeight(.76f),
                    navController = navController,
                    startDestination = Destination.premiumGrid)
                
            }
        })
    }

    object Destination {
        const val premium = "premium"
        const val locked = "locked"
        const val premiumGrid = "premium_grid"
        const val lockedGrid = "locked_grid"
    }

    @ExperimentalAnimationApi
    @Composable
    fun SetupNavGraph(
        modifier: Modifier,
        navController: NavHostController,
        startDestination: String
    ) {
        NavHost(
            navController = navController,
            modifier = modifier,
            startDestination = startDestination
        ) {
            composable(
                route = Destination.premium
            ) {
                AndroidViewBinding(ComposeFragmentSwlmPremiumBinding::inflate) {
                    premiumFragment = this.swlmPremium.getFragment()
                }
            }
            composable(
                route = Destination.premiumGrid
            ) {
                AndroidViewBinding(ComposeFragmentSwlmPremiumGridBinding::inflate) {
                    premiumGridFragment = this.swlmPremiumGrid.getFragment()
                }
            }
            composable(
                route = Destination.locked
            ) {
                AndroidViewBinding(ComposeFragmentSwlmLockedBinding::inflate) {
                    lockedFragment = this.swlmLocked.getFragment()
                }
            }
            composable(
                route = Destination.lockedGrid
            ) {
                AndroidViewBinding(ComposeFragmentSwlmLockedGridBinding::inflate) {
                    lockedGridFragment = this.swlmLockedGrid.getFragment()
                }
            }
        }
    }

    override fun onDestroyView() {
        if (::lockedFragment.isInitialized) {
            childFragmentManager.findFragmentById(lockedFragment.id)?.let { fragment ->
                childFragmentManager.beginTransaction().remove(fragment).commitAllowingStateLoss()
            }
        }
        if (::lockedGridFragment.isInitialized) {
            childFragmentManager.findFragmentById(lockedGridFragment.id)?.let { fragment ->
                childFragmentManager.beginTransaction().remove(fragment).commitAllowingStateLoss()
            }
        }
        if (::premiumFragment.isInitialized) {
            childFragmentManager.findFragmentById(premiumFragment.id)?.let { fragment ->
                childFragmentManager.beginTransaction().remove(fragment).commitAllowingStateLoss()
            }
        }
        if (::premiumGridFragment.isInitialized) {
            childFragmentManager.findFragmentById(premiumGridFragment.id)?.let { fragment ->
                childFragmentManager.beginTransaction().remove(fragment).commitAllowingStateLoss()
            }
        }
        super.onDestroyView()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        outState.clear()
        super.onSaveInstanceState(outState)
    }

    override fun onStop() {
        super.onStop()
        presenter.onStop()
    }

    private fun showAlertDialog(dialog: AlertDialog) {
        context?.let {
            val dialogBuilder =
                getAlertDialogBuilder(requireContext(), dialog, setCancelable = false)

            if (!requireActivity().isFinishing && requireActivity().isRunning()) {
                dialogBuilder.showAlertDialogWithDefaultInsets()
            }
        }
    }

    fun upgradeToPremium() {
        if (showGrid) {
            composeNavController.navigate(Destination.premiumGrid)
        } else {
            composeNavController.navigate(Destination.premium)
        }
    }


  
    // Presenter methods

    override fun updateState(state: SeeWhoLikesMePresenter.State) {
        requireActivity().runOnUiThread {
            when (state.dialog) {
                is AlertDialog -> showAlertDialog(state.dialog)
            }
        }
    }

    override fun navigateHome() {
        navController.navigate(MobileNavigationDirections.actionGlobalNavigationHome())
    }
}
Run Code Online (Sandbox Code Playgroud)

我不明白为什么我会遇到这个崩溃。它说使用子fragmentManager,而AndroidViewBinding似乎正在调用childFragmentManager。那么有人知道我做错了什么吗?