material-components-android
material-components-android copied to clipboard
[BottomSheetDialog] `AbstractComposeView` can't be inserted a layout file used to a `BottomSheetDialog`.
Description:
- I have one
BottomSheetDialog#setContentView(R.layout.earn_more_view). - Inside the layout file, I inserted
ComposeEarnMoreViewwho extends fromAbstractComposeView, and Of course, the@Composable override fun Content()was implemented byComposablefunctions. - My app just crashed when I tried to pop up the dialog.
And the main log looks like below:
FATAL EXCEPTION: main
Process: org.alias.staging, PID: 8224
java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.FrameLayout{b3ad65d V.E...... ......I. 0,0-0,0 #7f0902db app:id/container}
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer(WindowRecomposer.android.kt:352)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer$default(WindowRecomposer.android.kt:325)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion.LifecycleAware$lambda$0(WindowRecomposer.android.kt:168)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion.$r8$lambda$PmWZXv-2LDhDmANvYhil4YZYJuQ(Unknown Source:0)
at androidx.compose.ui.platform.WindowRecomposerFactory$Companion$$ExternalSyntheticLambda0.createRecomposer(Unknown Source:0)
at androidx.compose.ui.platform.WindowRecomposerPolicy.createAndInstallWindowRecomposer$ui_release(WindowRecomposer.android.kt:224)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:300)
at androidx.compose.ui.platform.AbstractComposeView.resolveParentCompositionContext(ComposeView.android.kt:244)
at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:251)
at androidx.compose.ui.platform.AbstractComposeView.onAttachedToWindow(ComposeView.android.kt:283)
at android.view.View.dispatchAttachedToWindow(View.java:21748)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3732)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3739)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3739)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3739)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3739)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3739)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3739)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3739)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3739)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3859)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:3146)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:11127)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1599)
at android.view.Choreographer.doCallbacks(Choreographer.java:1263)
at android.view.Choreographer.doFrame(Choreographer.java:1127)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1549)
at android.os.Handler.handleCallback(Handler.java:966)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:293)
at android.app.ActivityThread.loopProcess(ActivityThread.java:9986)
at android.app.ActivityThread.main(ActivityThread.java:9975)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:586)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1240)
Expected behavior: My app won't crash and the layout file should be shown in the BottomSheetDailog.
Source code: BottomSheetDialog or its parent AppCompatDialog or its delegate AppCompatDelegateImpl has no LifecycleOwner.
If we check the source code of androidx.activity.ComponentDialog, which implements LifecycleOwner and calledwindow!!.decorView.setViewTreeLifecycleOwner(this) when add/setContentView.
But BottomSheetDialog/AppCompatDialog/AppCompatDelegateImpl didn't.
Minimal sample app repro:
class CrashBottomSheetDialog(context: Context) : BottomSheetDialog(context) {
init {
setContentView(object : AbstractComposeView(context) {
@Composable
override fun Content() {
Text(text = "Hello, World")
}
})
}
}
Android API version: Android 10
Material Library version: com.google.android.material:material:1.9.0~1.12.0
Device: Huawei Mate 50
Solution:: I found this issue around May, 2023. I thought google team would fix this soon but actually they didn't. So I create an Issue for it to see if I could push google team to fix it inside the material library.
I had a solution like below, hoping it could inspire you guys somehow. Also I could create a PR like the solution below or create a PR inside AppCompatDelegateImpl. Tell me what you think.
class ComponentBottomSheetDialog @JvmOverloads constructor(
context: Context,
@StyleRes themeResId: Int = 0
) : BottomSheetDialog(context, themeResId),
LifecycleOwner,
SavedStateRegistryOwner {
private var _lifecycleRegistry: LifecycleRegistry? = null
private val lifecycleRegistry: LifecycleRegistry
get() = _lifecycleRegistry ?: LifecycleRegistry(this).also {
_lifecycleRegistry = it
}
private val savedStateRegistryController: SavedStateRegistryController =
SavedStateRegistryController.create(this)
override val savedStateRegistry: SavedStateRegistry
get() = savedStateRegistryController.savedStateRegistry
override val lifecycle: Lifecycle
get() = lifecycleRegistry
override fun onSaveInstanceState(): Bundle {
val bundle = super.onSaveInstanceState()
savedStateRegistryController.performSave(bundle)
return bundle
}
@Suppress("ClassVerificationFailure") // needed for onBackInvokedDispatcher call
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedStateRegistryController.performRestore(savedInstanceState)
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
@CallSuper
override fun onStart() {
super.onStart()
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
@CallSuper
override fun onStop() {
// This is the closest thing to onDestroy that a Dialog has
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
_lifecycleRegistry = null
super.onStop()
}
override fun setContentView(layoutResID: Int) {
initViewTreeOwners()
super.setContentView(layoutResID)
}
override fun setContentView(view: View) {
initViewTreeOwners()
super.setContentView(view)
}
override fun setContentView(view: View, params: ViewGroup.LayoutParams?) {
initViewTreeOwners()
super.setContentView(view, params)
}
override fun addContentView(view: View, params: ViewGroup.LayoutParams?) {
initViewTreeOwners()
super.addContentView(view, params)
}
private fun initViewTreeOwners() {
window!!.decorView.setViewTreeLifecycleOwner(this)
window!!.decorView.setViewTreeSavedStateRegistryOwner(this)
}
}