-
Notifications
You must be signed in to change notification settings - Fork 135
Description
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:
-
Update
androidx.appcompat:appcompat
to version1.3.1+
, discovered via this StackOverflow post and a few others;unfortunately this involved forkingRN since it still uses version 1.0.2 which is from 2018 (!!!). Compose depends on some lifecycle and saved-state logic inandroidx.activity.ComponentActivity
, whereasReactActivity
currently ends up extending from the same-name-but-different-packageandroidx.core.app.ComponentActivity
. -
Manually shim the missing logic using
ViewTreeLifecycleOwner
andViewTreeSavedStateRegistryOwner
; thankfullyReactActivity
already implementsLifecycleOwner
via the olderandroidx.core.app.ComponentActivity
, but you do need to addandroidx.savedstate:savedstate-ktx
version1.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 andI'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?