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。那么有人知道我做错了什么吗?
| 归档时间: |
|
| 查看次数: |
291 次 |
| 最近记录: |