Hey all :wave: Our team has a (fairly big) existin...
# multiplatform
t
Hey all 👋 Our team has a (fairly big) existing app built with react native, but we're thinking of slowly moving away from react native to KMP, with Jetpack Compose and SwiftUI for native UI. I've seen this promising library (though, not that popular) for combining KMP-shared-business-logic aspect and react-native-UI aspect, but I haven't been able to find many resources on how to actually add on e.g. Composable screens on top of the current app. So if I want to create a new Composable screen called
MoreMenuScreen
, navigate to it, as well as have a shared VM for it, I'm not sure how I'd do that with the current react native setup. The hope is that eventually react native will slowly be replaced, but we can't do that in one go as new features still need to be released. If anyone's done anything similar, any advice and resources would be greatly appreciated! Thank you!
s
I don’t have much experience with react native and don’t know how useful the library you have linked above is but if you just want to share the business logic; I would just create a Kotlin Multiplatform module and generate a js library out of it and use it in react native. Once you start migrating into kmp for ui ,you can use the same module/library as it is already in kotlin.
t
Yeah I think that's pretty much what that library I linked does for the shared code, if I understand it correctly. I'm a little more puzzled on the UI side of things rather than the business logic. Things I've seen about using Compose components with react-native seem more of a "one-off component" instead of "many many screens that from now on will be in Compose instead of react-native", so that's what I'm trying to figure out how to do now
f
First you need to migrate the business logic, since the UI generally depends on the business logic and you cannot have a dependency from KMP to js. If you do, then you have to use kotlin/js, not KMP, in which case you would just be changing the language, not the platform (kotlin/js would still run in RN). Once you migrated the business logic, then you can migrate the simply rewriting it. The intermediate possible step here is to have the business logic in KMP and the UI in RN. That is possible because RN can depend on native modules. The library you linked allows you to do this in a simple way: create native modules for both platforms without caring about hand-writing a lot of the native modules bindings, and with a native implementation that is based on KMP (instead of Kotlin + Swift for example). If you have independent features, you can migrate them independently. Off topic, may I ask you why you decided to switch technology from RN to KMP, and the scale of your app? (e.g. how many MAU)
t
Just to make sure I'm following what you're saying - it will be difficult to start adding new compose/swiftui screens and integrating that with the current RN set-up because those will be tied to the business logic? So we need to spend some time initially to migrate the business logic before we can start adding new features? There are several reasons really 1. There's a handover of a codebase, and the new engineers (myself included) are generally more experienced with compose, swift UI, and kotlin than react native 2. We want to give the apps a more native feel than they currently have, and we believe moving away from react native will help us do that. But of course still want to share the business logic as we've found on other projects that the efficiency gains are huge 3. Compose is awesome 😄 4. I've read about KMP (generally) performing better than RN due to utilization of native components The scale of the app is quite large, MAU is in the low millions.
m
I happen to know a ton about RN's android internals. Not so much about iOs, though.
If you want to combine compose and RN, you would have to expose the composition itself as a part of an RN module. Then, since compositions are functions, and therefore can be housed within companion objects, you could reuse all of them outside of RN
your migration route depends on how much JS do you have within your critical path. Let's say, JS controls everything in your app. Embedding compose inside it the RN tree won't do much good to you, since is a ReactView/ReactRootView (android view capable of housing an android view tree spawned from the shadow tree created by the react code), and a composable would be inserted there by using AndroidView, so is just overhead (It would help with the design, though, and you could handle animation and touch at the android level instead of having JS deal with that and communicate through Event Emitters). If you want to have compose screens and react screens instead, all you have to do is either keep a reference to the base framelayout every activity has (android.R.id.content) and keep a ComposeView (setContent actually places one for you) and ReactRootView, both as full screen children of the Framelayout, or just add a FrameLayout with both children
from that point, your React app renders within the ReactRootView (You need to either inherit from ReactActivity or just implement the same callbacks that class has), and your compositions renders inside the ComposeView. Build a singleton housing the function that allows swapping the visibility using an interface or something like that, and create a native RN module that consumes the singleton (RN modules are usually gradle submodules, hence the need for a singleton with a weak reference to the activity through an interface). From the compose side, you call the same function when you need to swap back to React
or, just use a DeviceEventEmiiter (I don't recommend that if your logic uses that thing, or any JS code extensively, because it just chokes itself running callbacks)
Either with regular modules, or JSI based modules (turbomodules), you would be able to send state from react to compose as well. So your logic does not need to change a lot at the beginning. And nothing stops you from sending those states to native android before they are actually needed, so the invisible composition is always ready.
at that point, you will have a bunch of stateless composables you can use with or without react-native, and you can start migrating logic. If you do need, or want, to keep using RN screens with native state and logic management (which is kinda the only thiung you should do with RN, considering Meta's usage and support of the thing), just keep creating singletons to handle your logic, and native RN modules to expose those singletons without having to bind your logic to React-native, or just keep a wrapped instance of DeviceEventEmitter to feed state to a react context from native. That depends on who's driving the UI at that point, events handled by JS, or just pure state.
Word of advice, if you have code in RN that's being driven by AppStatus (background/foreground), you may have issues when swapping, since RN may consider itself "backgrounded" (RN uses the "my rootview is kinda obscured by something" event as the definition of "background", so if you spawn dialogs or things like that AppStatus will swear the app is backgrounded. In such a case, just replace it with a context fed with proper android lifecycle states)
t
Wow this is a fantastic explanation, thank you very much Francisco! Out of curiosity, is this knowledge just from experience or are there any additional resources you could recommend that discuss the setup you're talking about?
m
Both experience and experimentation, my job was to find out sources of slowness and bugs within a RN app and solve them somehow
kinda "we really want to settle in front of Mordor's doors, all you have to do is guard it"
😄 1
Just hit me if you have any questions
thank you color 1