Adding ComposeView to PopupWindow's contentView crashes
Asked Answered
G

2

10

I'm trying to show a PopupWindow from a Fragment, and then add a ComposableView to the Popup contentView, and it crashes because the ViewTreeLifecycleOwner is not found for PopupDecorView$PopupDecorView.

Things I've tried:

  • Use the PopupWindowCompat to display the popup.
  • Multiple lib versions for Fragment (1.3.4 and 1.4.0-alpha1).
  • Activity version that I'm using is: 1.3.0-alpha08.
  • Manually set the lifecycle owners for contentView and ComposeView.

Calls:

Add ComposeView to contentView:

 contentWindow.contentView.addView(
                AdaptiveCardistRender.adaptiveCardistResponseView(
                    contentWindow.contentView.context,
                    response
                )
            )

Show Popup:

PopupWindowCompat.showAsDropDown(
                popupWindow,
                anchor,
                0,
                startVerticalOffset,
                Gravity.NO_GRAVITY
            )

Set lifecycle on the PopupWindow:

class ExtensionsPopupWindow(
    val contentView: ScrollView,
    width: Int,
    height: Int,
    lifecycleOwner: LifecycleOwner,
    savedStateRegistryOwner: SavedStateRegistryOwner,
) : PopupWindow(contentView, width, height, true) {

    init {
        ViewTreeLifecycleOwner.set(contentView, lifecycleOwner)
        ViewTreeSavedStateRegistryOwner.set(contentView, savedStateRegistryOwner)
    }

Stacktrace:

    java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.PopupWindow$PopupDecorView{9dfea2f V.E...... R.....I. 0,0-0,0}
        at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:242)
        at androidx.compose.ui.platform.WindowRecomposer_androidKt.access$createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:1)
        at androidx.compose.ui.platform.WindowRecomposerFactory$Companion$LifecycleAware$1.createRecomposer(WindowRecomposer.android.kt:98)
        at androidx.compose.ui.platform.WindowRecomposerPolicy.createAndInstallWindowRecomposer$ui_release(WindowRecomposer.android.kt:153)
        at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:228)
        at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:200)
        at androidx.compose.ui.platform.AbstractComposeView.onAttachedToWindow(ComposeView.android.kt:235)
        at android.view.View.dispatchAttachedToWindow(View.java:20665)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3493)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3500)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3500)
        at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3500)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2544)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2057)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8501)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1005)
        at android.view.Choreographer.doCallbacks(Choreographer.java:826)
        at android.view.Choreographer.doFrame(Choreographer.java:761)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:990)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7727)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
Gavrilla answered 26/5, 2021 at 23:50 Comment(5)
Are you using implementation 'androidx.appcompat:appcompat:1.3.0' ?Twana
Yup, I've forgotten to mention that as wellGavrilla
Can you add some code to have a reproducible example?Twana
Have you got the answer for this? I'm having the same issue in my project. If you have the answer, please, share with me :)Radiobiology
No answer yet, ended up by using BottomSheet :(Gavrilla
A
2

By default PopupWindow cannot work with compose.

There is some setup to do for compose to find :

  • Content child
  • ViewTreeLifecycleOwner,
  • ViewTreeSavedStateRegistryOwner.

Here an example of how to show a popup from compose with a compose view inside the popup window.
(the most important part is the parentView)

val composeView = ComposeView(LocalContext.current).apply {
    setContent {
        // your composables
    }
}
val parentView = FrameLayout(LocalContext.current).apply {
    // Help compose to find View.contentChild
    id = android.R.id.content
    // Help compose to find ViewTreeLifecycleOwner
    ViewTreeLifecycleOwner.set(this, LocalLifecycleOwner.current)
    // Help compose to find ViewTreeSavedStateRegistryOwner
    ViewTreeSavedStateRegistryOwner.set(this, LocalSavedStateRegistryOwner.current)

    layoutParams = FrameLayout.LayoutParams(
        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
    )
    addView(composeView)

}
val popupWindow = PopupWindow(
    parentView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
)

popupWindow.showAtLocation(LocalView.current, Gravity.CENTER, 0, 0)

DisposableEffect(Unit) {
    onDispose {
        popupWindow.dismiss()
    }
}

If you do it from a activity replace LocalLifecycleOwner.current, LocalSavedStateRegistryOwner.current and LocalContext.current by your activity.

Anjelicaanjou answered 25/2, 2022 at 6:3 Comment(1)
I hope it works but i've again a crash with this error : Attempt to invoke virtual method 'android.view.View android.view.View.getRootView()' on a null object referenceJipijapa
U
0

I had the same issue when I tried to put AbstractComposeView in window popup as a workaround you can use compose Popup and still use the compose view as place holder inside view (it doesn't require height or width so it will not mess with your layout) then just show the popup with IntOffset where you want in the screen

Ury answered 6/1, 2022 at 12:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.