material-components-android icon indicating copy to clipboard operation
material-components-android copied to clipboard

[BottomSheetDialog] `AbstractComposeView` can't be inserted a layout file used to a `BottomSheetDialog`.

Open bytebeats opened this issue 1 year ago • 0 comments
trafficstars

Description:

  • I have one BottomSheetDialog#setContentView(R.layout.earn_more_view).
  • Inside the layout file, I inserted ComposeEarnMoreView who extends from AbstractComposeView, and Of course, the @Composable override fun Content() was implemented by Composable functions.
  • 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)
  }
}

bytebeats avatar Jun 25 '24 09:06 bytebeats