flexbox-layout icon indicating copy to clipboard operation
flexbox-layout copied to clipboard

ComposeView in flexbox crashes, because it is not attached when measured

Open Direwolfik opened this issue 1 year ago • 0 comments

  • [x] I have searched existing issues and confirmed this is not a duplicate

Issues and steps to reproduce

When I try to use ComposeView or AbstractComposeView with FlexboxLayoutManager, it crashes due to

 java.lang.IllegalStateException: Cannot locate windowRecomposer; View androidx.compose.ui.platform.ComposeView{7ae95e1 V.E...... ......I. 0,0-0,0} is not attached to a window
                                                                                                    	at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:294)
                                                                                                    	at androidx.compose.ui.platform.AbstractComposeView.resolveParentCompositionContext(ComposeView.android.kt:242)
                                                                                                    	at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:249)
...
                                                                                                    	at android.view.View.measure(View.java:25466)
                                                                                                    	at com.google.android.flexbox.FlexboxHelper.calculateFlexLines(FlexboxHelper.java:477)
                                                                                                    	at com.google.android.flexbox.FlexboxHelper.calculateHorizontalFlexLines(FlexboxHelper.java:249)
                                                                                                    	at com.google.android.flexbox.FlexboxLayoutManager.updateLayoutState(FlexboxLayoutManager.java:2112)
                                                                                                    	at com.google.android.flexbox.FlexboxLayoutManager.handleScrollingMainOrientation(FlexboxLayoutManager.java:1993)
                                                                                                    	at com.google.android.flexbox.FlexboxLayoutManager.scrollVerticallyBy(FlexboxLayoutManager.java:1957)
                                                                                                    	at androidx.recyclerview.widget.RecyclerView.scrollStep(RecyclerView.java:2009)

This basically prevents usage of FlexboxLayoutManager with compose views.

Root cause is that FlexboxLayoutManager attempts to measure composable before it is attached to window, which crashes in ComposeView:

/**
 * Get or lazily create a [Recomposer] for this view's window. The view must be attached
 * to a window with a [ViewTreeLifecycleOwner] registered at the root to access this property.
 */
@OptIn(InternalComposeUiApi::class)
internal val View.windowRecomposer: Recomposer
    get() {
        check(isAttachedToWindow) {
            "Cannot locate windowRecomposer; View $this is not attached to a window"
        }
        val rootView = contentChild
        return when (val rootParentRef = rootView.compositionContext) {
            null -> WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView)
            is Recomposer -> rootParentRef
            else -> error("root viewTreeParentCompositionContext is not a Recomposer")
        }
    }

Expected behavior

It should not crash, as it does not crash with other managers (e.g. LinearLayourManager)

Version of the flexbox library

Flexbox: 3.0.0 Compose: 1.3.0 Recycler view: 1.3.0-rc1

Link to code

class ComposeAdapter : RecyclerView.Adapter<ComposeHolder>() {

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int,
    ): MyComposeViewHolder {
        return ComposeHolder(ComposeView(parent.context))
    }

class MyComposeViewHolder(
    val composeView: ComposeView
) : RecyclerView.ViewHolder(composeView) {

    fun bind(input: String) {
        composeView.setContent {
            MdcTheme {
                Text(input)
            }
        }
    }
}

Direwolfik avatar Nov 02 '22 12:11 Direwolfik