Resolve crash when changing orientation with settings open.

This commit is contained in:
Alex Hart
2026-07-30 12:21:04 -03:00
parent 271cf81c49
commit a30fd9fec4
3 changed files with 111 additions and 1 deletions
@@ -30,6 +30,7 @@ import kotlinx.coroutines.launch
import org.signal.core.ui.navigation.TransitionSpecs
import org.thoughtcrime.securesms.MainNavigator
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsNavHostFragment
import org.thoughtcrime.securesms.compose.AndroidNavHostFragment
import org.thoughtcrime.securesms.compose.FragmentBackHandler
import org.thoughtcrime.securesms.compose.FragmentBackPressedState
import org.thoughtcrime.securesms.conversation.ConversationIntents
@@ -141,7 +142,7 @@ private fun ConversationSettingsEntry(route: MainNavigationDetailLocation.Chats.
val backPressedState = remember { FragmentBackPressedState() }
FragmentBackHandler(backPressedState)
AndroidFragment(
AndroidNavHostFragment(
clazz = ConversationSettingsNavHostFragment::class.java,
fragmentState = fragmentState,
arguments = args,
@@ -0,0 +1,107 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.compose
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.commitNow
import androidx.fragment.compose.AndroidFragment
import androidx.fragment.compose.FragmentState
import androidx.fragment.compose.rememberFragmentState
import androidx.navigation.fragment.NavHostFragment
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import org.thoughtcrime.securesms.R
/**
* Hosts a [NavHostFragment] inside Compose.
*
* [AndroidFragment] cannot host a [NavHostFragment] directly. It gives its container a
* composition-derived id, and [NavHostFragment] reuses its own container id for the destinations it
* navigates to. That id is written into the saved child-fragment state, so once the screen is
* restored the child [androidx.fragment.app.FragmentManager] looks for a container id that no longer
* exists and throws "No view found for id ...".
*
* Interposing [NavHostWrapperFragment] gives the nav host a container with a stable resource id,
* which survives configuration changes and process death.
*/
@Composable
fun AndroidNavHostFragment(
clazz: Class<out NavHostFragment>,
modifier: Modifier = Modifier,
fragmentState: FragmentState = rememberFragmentState(),
arguments: Bundle = Bundle.EMPTY,
onUpdate: (Fragment) -> Unit = {}
) {
val wrapperArguments = remember(clazz, arguments) { NavHostWrapperFragment.createArgs(clazz, arguments) }
AndroidFragment(
clazz = NavHostWrapperFragment::class.java,
modifier = modifier,
fragmentState = fragmentState,
arguments = wrapperArguments,
onUpdate = onUpdate
)
}
/**
* Hosts a [NavHostFragment] in a container with a stable id. Use via [AndroidNavHostFragment].
*/
class NavHostWrapperFragment : Fragment(), FragmentBackPressedInfoProvider {
companion object {
private const val ARG_NAV_HOST_CLASS_NAME = "nav_host_wrapper.class_name"
private const val ARG_NAV_HOST_ARGUMENTS = "nav_host_wrapper.arguments"
fun createArgs(clazz: Class<out NavHostFragment>, arguments: Bundle): Bundle {
return bundleOf(
ARG_NAV_HOST_CLASS_NAME to clazz.name,
ARG_NAV_HOST_ARGUMENTS to arguments
)
}
}
private val navHostFragment: NavHostFragment?
get() = childFragmentManager.findFragmentById(R.id.nav_host_wrapper_container) as? NavHostFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (navHostFragment != null) {
return
}
val args = requireArguments()
val className = requireNotNull(args.getString(ARG_NAV_HOST_CLASS_NAME)) { "No nav host class name in arguments." }
val navHostClass = FragmentFactory.loadFragmentClass(requireContext().classLoader, className)
childFragmentManager.commitNow {
setReorderingAllowed(true)
add(R.id.nav_host_wrapper_container, navHostClass, args.getBundle(ARG_NAV_HOST_ARGUMENTS))
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return FragmentContainerView(inflater.context).apply {
id = R.id.nav_host_wrapper_container
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
}
override fun getFragmentBackPressedInfo(): Flow<FragmentBackPressedInfo> {
val provider = navHostFragment as? FragmentBackPressedInfoProvider
return provider?.getFragmentBackPressedInfo() ?: flowOf(FragmentBackPressedInfo.Disabled)
}
}
+2
View File
@@ -32,4 +32,6 @@
<item name="edittext_monospace" type="id" />
<item name="edittext_spoiler" type="id" />
<item name="edittext_clear_formatting" type="id" />
<item name="nav_host_wrapper_container" type="id" />
</resources>