Skip to content

[Android] Discussion: Support for Jetpack Compose #446

@thevoiceless

Description

@thevoiceless

Status as of April 2024

You should be able to use Compose with React Native, but it does not play nice with react-native-screens


The update to Gradle 7+ in RN 0.66+ means that we can now use Jetpack Compose to build React-like declarative UIs natively on Android. It's completely separate from the traditional View system but there are interoperability APIs to bridge the two worlds.

Thanks to AbstractComposeView, I was able to implement a basic proof-of-concept (edited for brevity):

class MyViewManager : SimpleViewManager<MyView>() {
    override fun createViewInstance(reactContext: ThemedReactContext) = MyView(reactContext)

    @ReactProp(name = "displayText")
    fun displayText(view: MyView, displayText: String) {
        view.displayText = displayText
    }
}

class MyView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
) : AbstractComposeView(context, attrs) {
    var displayText by mutableStateOf("")

    @Composable
    override fun Content() {
        Text(displayText)
    }
}

However!

Release builds crash with an IllegalStateException: ViewTreeLifecycleOwner not found ... when trying to display the Compose UI content. I haven't been able to nail down the exact reason why this only occurs in release builds, but I was able to figure out two workarounds:

  1. Update androidx.appcompat:appcompat to version 1.3.1+, discovered via this StackOverflow post and a few others; unfortunately this involved forking RN since it still uses version 1.0.2 which is from 2018 (!!!). Compose depends on some lifecycle and saved-state logic in androidx.activity.ComponentActivity, whereas ReactActivity currently ends up extending from the same-name-but-different-package androidx.core.app.ComponentActivity.

  2. Manually shim the missing logic using ViewTreeLifecycleOwner and ViewTreeSavedStateRegistryOwner; thankfully ReactActivity already implements LifecycleOwner via the older androidx.core.app.ComponentActivity, but you do need to add androidx.savedstate:savedstate-ktx version 1.1.0+ to your dependencies:

abstract class MyReactActivityDelegate(
    activity: ReactActivity,
    mainComponentName: String?,
) : ReactActivityDelegate(activity, mainComponentName) {

    private val shim = SavedStateRegistryOwnerShim(activity)

    @CallSuper
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        shim.onCreate(savedInstanceState)
    }

    private class SavedStateRegistryOwnerShim(
        private val activity: AppCompatActivity,
    ) : LifecycleOwner by activity, SavedStateRegistryOwner {

        private val controller = SavedStateRegistryController.create(this)
        override fun getSavedStateRegistry() = controller.savedStateRegistry

        fun onCreate(savedState: Bundle?) {
            activity.window.decorView.rootView.let { root ->
                ViewTreeLifecycleOwner.set(root, this)
                ViewTreeSavedStateRegistryOwner.set(root, this)
            }
            controller.performRestore(savedState)
        }
    }
}

class MyActivity : ReactActivity() {
    override fun createReactActivityDelegate() {
        return object : MyReactActivityDelegate(this, mainComponentName) { ... }
    }
}

As far as I can tell, both approaches prevent the crash and Compose seems to function as expected. However:

  • Option 1 involves forking RN and I'm not sure how the change may affect RN overall. I also assume that Compose will eventually require newer and newer AndroidX dependencies, so it would be nice to have them "officially" updated.

  • Option 2 is a kludge that may need to be periodically updated to match the AndroidX implementation, and I have no idea if it even fully implements all of the plumbing that Compose expects under the hood.

So, any chance we could get androidx.appcompat:appcompat updated to at least 1.3.1?

Or better yet, some kind of official support for Compose? Perhaps a SimpleComposeViewManager that directly accepts @Composable content?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions