As someone deeply familiar with Android’s Jetpack Compose (and somewhat familiar with SwiftUI), exploring React Native with Expo has been quite the experience. All three frameworks share a declarative UI pattern, which surprisingly made the transition to React Native much easier than expected. After spending some time creating a multi-platform app, there are a few bigger differences (and similarities) that I noticed coming from a Compose background.
UI Components: Familiar Territory
Creating reusable UI components is an important part of maintaining a large app and consistency in design. Luckily React Native’s component structure is familiar to the work of Compose and SwiftUI.
Jetpack Compose
In Compose, we might write a reusable Composable that applies two texts into a column on a framework supplied Card Composable
|
|
SwiftUI
Utilizing SwiftUI, a similar Card view would have to be custom created in order to match the Jetpack Compose version. This can be done a few ways, but I’ve included an example of using a custom modifier (with the help of the expert iOS developers at Atomic Robot)
|
|
React Native
Creating a similar cross-platform view in a React Native project, the pattern is remarkably similar yet has a hint of web-like tags. I struggled most here with Typescript as it is also my first experience with this language.
|
|
State Management: Different Yet Not Far Off
While each framework has its own syntax and conventions, the underlying concepts remain similar. React Native’s approach with hooks provides a clean, functional way to manage component state that will feel familiar to developers coming from either SwiftUI or Jetpack Compose.
They’re all built upon the same fundamental concept: declarative UI updates driven by state changes.
The following example of an expandable component is a great real world implementation of state management.
Jetpack Compose
Android Compose utilizes remember
along with a type of mutableState
. Mutable state types allow for direct assignment of the state value.
|
|
SwiftUI
Swift uses a @State
property wrapper. This allows for direct mutation of a boolean state like isExpanded
with .toggle()
|
|
React Native
So coming from the previous two frameworks, the React Native approach isn’t that far off from the familiar. It has a concept of a useState
hook that can be applied to a value variable along with a setter function. The setter function is then used to update the state properly.
|
|
Navigation: Routes vs Compose Navigation
One of the biggest changes that I had to wrap my head around with React Native was navigation. While they're fundamentally similar, React Native Expo specifically utilizes file based routing.
Jetpack Compose Navigation
Compose’s NavHost and Navigation Controller is heavily integrated with Android’s navigation component, offering a type-safe way to handle navigation:
|
|
SwiftUI Navigation
SwiftUI utilizes NavigationStack
and NavigationLink
to setup relationships between screens within the app.
|
|
React Native with Expo Router
Expo Router introduces a file-system based routing approach that might feel unfamiliar at first but becomes intuitive quickly:
|
|
The key differences I noticed:
-
File-Based Routing: Expo Router’s file-system based routing feels more web-like compared to Compose’s programmatic route definitions. Instead of explicitly defining routes, the file structure itself defines the navigation hierarchy.
-
Type Safety: While Compose Navigation provides compile-time safety for route arguments, Expo Router requires additional type definitions and runtime checks. However, TypeScript helps bridge this gap significantly.
-
Navigation State: React Native’s navigation state management feels more explicit with hooks like
useRouter()
anduseLocalSearchParams()
, whereas Compose’sNavController
and SwiftUI’s navigation state are more tightly integrated into the framework.
Development Experience
Across all three platforms, there are some noticeable similarities and differences:
- Hot Reload: Both Compose and React Native excel here, though Expo’s implementation feels more stable at times and is more responsive.
- Preview: Compose’s
@Preview
annotation and SwiftUI’s Previews are missed, but Expo’s live reload partially makes up for it. I don’t feel like I’m missing the quick development of components because of how quickly the reloads occur. It does require a change of mindset in implementing a UI component somewhere in the app so that the changes are visible. - Component Isolation: Android, iOS, and React Native frameworks encourage building and testing components in isolation.
Conclusion
As an Android developer, the transition to React Native from Jetpack Compose (and SwiftUI) feels more natural than it might for developers coming from traditional Android XML layouts. The declarative nature of both frameworks, component-based architecture, and state management patterns share enough similarities that it’s easy enough to make that mental swap.
The main adjustments I experienced were:
- Learning JavaScript/TypeScript languages and their ecosystem
- Adapting to React’s hooks system and the file based navigation
- Understanding cross-platform considerations and how to make updates that are platform dependent
- Working with different styling paradigms
However, the mental model of building UIs remains surprisingly similar. If you’re comfortable with Compose (and even SwiftUI or other declarative UI patterns), you’ll feel right at home in React Native. It just requires embracing a new language and ecosystem.
Looking to bridge the gap between native and React Native development? Atomic Robot’s team of native platform experts can help guide your cross-platform strategy while maintaining the polish and performance your users expect. Reach out to us at Atomic Robot — we’d be happy to help.