Dave
Dave

Reputation: 1966

Jetpack Compose Interoperability with XML Preview

Per https://developer.android.com/jetpack/compose/interop/interop-apis , ComposeView and AbstractComposeView should facilitate interoperability between Compose and existing XML based apps.

I've had some success when deploying to a device, but XML previews that include Compose elements aren't working for me.
As a simplified example, consider:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>

    <variable
        name="state"
        type="ObservableState" />
</data>


<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TestAtom
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:state="@{state}" />

</LinearLayout>

Making use of the following custom view file:

data class ObservableState(val text: ObservableField<String> = ObservableField("Uninitialized"))

data class State(val text: MutableState<String> = mutableStateOf(String()))

class TestAtom
@JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AbstractComposeView(context, attrs, defStyleAttr) {

    val state by mutableStateOf(State())

    fun setState(observableState: ObservableState?) {
        state.text.value = observableState?.text?.get() ?: "null"
    }

    @Composable
    override fun Content() {
        if (isInEditMode) {
            val text = remember { mutableStateOf("Hello Edit Mode Preview!") }
            TestAtomCompose(State(text = text))
        } else {
            TestAtomCompose(state)
        }
    }
}

@Composable
fun TestAtomCompose(state: State) {
    Text(text = "Text: " + state.text.value, modifier = Modifier.wrapContentSize())
}

@Preview(name = "Test", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable
fun PreviewTestCompose() {
    val text = remember { mutableStateOf("Hello World!") }
    TestAtomCompose(State(text = text))
}

I have tried many variations and iterations on this, however none successfully allowed a Compose-based custom view to render in an XML layout preview. (the @Compose preview works as expected) The above example results in a preview render error: java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.LinearLayout.

1) Has anyone found a tactic to allow complex Compose-based custom views to render in XML preview panes?

2) Is there a way to transmit XML preview pane selection options, like theme and light/dark mode, to an embedded ComposeView to update its preview?

3) Is there a way to assign specific sample data from XML to a ComposeView to cause it to render differently in the preview? (similar to tools: functionality)

Upvotes: 12

Views: 3120

Answers (1)

Lu No
Lu No

Reputation: 61

In order to preview in XML design a custom ComposableView that extend from AbstractComposeView you will need to tell to your view to use a custom recomposer.

AfzalivE from GitHub provide a gist about that: https://gist.github.com/AfzalivE/43dcef66d7ae234ea6afd62e6d0d2d37

Copy this file in your project, then override the onAttachToWindow method with the following:

override fun onAttachedToWindow() {
    if (isInEditMode) {
        setupEditMode()
    }
    super.onAttachedToWindow()
}

FYI: Google is working on a fix about this issue: https://issuetracker.google.com/u/1/issues/187339385?pli=1

Upvotes: 1

Related Questions