From react-native-hifi
Provides native Jetpack Compose UI components including LazyColumn scrolling lists and XML vector icons for Android-specific screens in Expo React Native apps.
npx claudepluginhub bidah/react-native-hifi --plugin react-native-hifiThis skill uses the workspace's default tool permissions.
`@expo/ui/jetpack-compose` provides Android-native Jetpack Compose views directly in React Native Expo apps. These components render using Android's modern declarative UI framework, giving you truly native Android look-and-feel without writing Kotlin.
Integrates Jetpack Compose Views and modifiers into Expo React Native apps via @expo/ui/jetpack-compose. For Android UI in SDK 55 with Host wrapper and API mirroring.
Guides using @expo/ui/jetpack-compose to build native Android UI in Expo SDK 55 apps with Jetpack Compose views, modifiers, and Host wrapper. Covers LazyColumn, Icon, and API mirroring.
Install and use Jetpack Compose components/modifiers like LazyColumn and Icon in Expo SDK 55 apps via @expo/ui/jetpack-compose, with API and docs guidance.
Share bugs, ideas, or general feedback.
@expo/ui/jetpack-compose provides Android-native Jetpack Compose views directly in React Native Expo apps. These components render using Android's modern declarative UI framework, giving you truly native Android look-and-feel without writing Kotlin.
Core principle: Use platform-specific @expo/ui imports to get native-quality components that follow each platform's design language. On Android, this means Jetpack Compose.
// Android-specific import
import { Button, LazyColumn, Icon } from '@expo/ui/jetpack-compose';
// For platform-specific rendering
import { Platform } from 'react-native';
function MyComponent() {
if (Platform.OS === 'android') {
return <AndroidView />;
}
return <IOSView />;
}
LazyColumn is the Jetpack Compose equivalent of RecyclerView. It lazily composes and lays out only the visible items, making it efficient for large datasets.
import { LazyColumn, Section, Item, Text } from '@expo/ui/jetpack-compose';
function ContactList() {
const contacts = [
{ id: '1', name: 'Alice', phone: '555-0101' },
{ id: '2', name: 'Bob', phone: '555-0102' },
{ id: '3', name: 'Charlie', phone: '555-0103' },
];
return (
<LazyColumn style={{ flex: 1 }}>
<Section header="Contacts">
{contacts.map((contact) => (
<Item key={contact.id}>
<Text style={{ fontSize: 16, fontWeight: 'bold' }}>
{contact.name}
</Text>
<Text style={{ fontSize: 14, color: '#666' }}>
{contact.phone}
</Text>
</Item>
))}
</Section>
</LazyColumn>
);
}
import { LazyColumn, Section, Item, Text } from '@expo/ui/jetpack-compose';
function SettingsScreen() {
return (
<LazyColumn style={{ flex: 1 }}>
<Section header="Account">
<Item onPress={() => console.log('Profile')}>
<Text>Profile</Text>
</Item>
<Item onPress={() => console.log('Security')}>
<Text>Security</Text>
</Item>
</Section>
<Section header="Preferences">
<Item onPress={() => console.log('Notifications')}>
<Text>Notifications</Text>
</Item>
<Item onPress={() => console.log('Theme')}>
<Text>Theme</Text>
</Item>
</Section>
<Section header="About">
<Item>
<Text>Version 1.0.0</Text>
</Item>
</Section>
</LazyColumn>
);
}
Render Android XML vector drawables as icons:
import { Icon } from '@expo/ui/jetpack-compose';
function NavigationBar() {
return (
<View style={{ flexDirection: 'row', justifyContent: 'space-around' }}>
<Icon
name="home"
size={24}
tintColor="#333"
/>
<Icon
name="search"
size={24}
tintColor="#333"
/>
<Icon
name="settings"
size={24}
tintColor="#333"
/>
</View>
);
}
import { Icon } from '@expo/ui/jetpack-compose';
// Material Design icons available by name
<Icon name="favorite" size={24} tintColor="red" />
<Icon name="share" size={24} tintColor="#1976D2" />
<Icon name="more_vert" size={24} tintColor="#757575" />
Native Material Design 3 buttons:
import { Button } from '@expo/ui/jetpack-compose';
function ActionButtons() {
return (
<View style={{ gap: 12 }}>
<Button
title="Filled Button"
onPress={() => console.log('Pressed')}
variant="filled"
/>
<Button
title="Outlined Button"
onPress={() => console.log('Pressed')}
variant="outlined"
/>
<Button
title="Text Button"
onPress={() => console.log('Pressed')}
variant="text"
/>
</View>
);
}
When you need to wrap Jetpack Compose views for use within React Native's view hierarchy:
import { requireNativeView } from 'expo';
// Wrap a native Android Compose component
const NativeComposeView = requireNativeView('MyComposeView');
function ComposeWrapper({ data, onAction }) {
return (
<NativeComposeView
style={{ flex: 1 }}
data={data}
onAction={onAction}
/>
);
}
If you need a Compose component not available in @expo/ui:
// android/src/main/java/com/myapp/MyComposeView.kt
package com.myapp
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class MyComposeModule : Module() {
override fun definition() = ModuleDefinition {
Name("MyComposeView")
View(MyComposeView::class) {
Prop("title") { view: MyComposeView, title: String ->
view.setTitle(title)
}
Events("onAction")
}
}
}
Pattern for using Jetpack Compose components alongside cross-platform code:
import { Platform, View, Text } from 'react-native';
// Lazy imports to avoid loading on wrong platform
const AndroidList = Platform.OS === 'android'
? require('@expo/ui/jetpack-compose').LazyColumn
: null;
function AdaptiveList({ items }) {
if (Platform.OS === 'android' && AndroidList) {
return (
<AndroidList style={{ flex: 1 }}>
{/* Android-native rendering */}
</AndroidList>
);
}
// Fallback for iOS/web
return (
<FlatList
data={items}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
);
}
For larger platform differences, use Expo Router's platform file extensions:
components/
SettingsList.tsx # Shared/default
SettingsList.android.tsx # Android with Jetpack Compose
SettingsList.ios.tsx # iOS with SwiftUI
// components/SettingsList.android.tsx
import { LazyColumn, Section, Item, Text } from '@expo/ui/jetpack-compose';
export function SettingsList({ sections }) {
return (
<LazyColumn style={{ flex: 1 }}>
{sections.map((section) => (
<Section key={section.title} header={section.title}>
{section.items.map((item) => (
<Item key={item.id} onPress={item.onPress}>
<Text>{item.label}</Text>
</Item>
))}
</Section>
))}
</LazyColumn>
);
}
| Mistake | Fix |
|---|---|
Importing @expo/ui/jetpack-compose on iOS | Guard imports with Platform.OS check or use platform file extensions |
Using FlatList patterns with LazyColumn | LazyColumn uses Section/Item children, not renderItem |
| Forgetting to rebuild after adding native dependency | Run eas build or npx expo run:android after adding @expo/ui |
| Mixing Compose and View styling | Compose components have their own styling; use props, not StyleSheet |
| Expecting identical rendering across platforms | Jetpack Compose follows Material Design; iOS follows Human Interface Guidelines |
| Task | Pattern |
|---|---|
| Import Compose components | import { ... } from '@expo/ui/jetpack-compose' |
| Scrolling list | <LazyColumn> with <Section> and <Item> |
| Icon | <Icon name="icon_name" size={24} /> |
| Button | <Button title="Label" variant="filled" onPress={fn} /> |
| Platform guard | Platform.OS === 'android' or .android.tsx file |
| Custom native view | requireNativeView('ViewName') |
| Host wrapping | Expo Modules API with Kotlin + Compose |