This example uses Expo with LiveKit’s React Native WebRTC stack. React Native realtime requires the LiveKit packages and an early
registerGlobals() call — plain react-native-webrtc is not supported. See LiveKit setup for React Native below.What you’ll build
Model switching
Switch between Lucy Restyle Live, Lucy editing, and — after a quick upgrade — Lucy 2.1 for character transformation.
Style presets
Swipe through curated looks: anime characters, tuxedos, superhero costumes, and more. Each preset is a text prompt sent to the model.
Camera controls
Toggle front/back camera, switch view modes (fullscreen, picture-in-picture, split), and see your original and transformed feeds side by side.
Prerequisites
- Node.js 18+
- Expo (
npx expo— no global install needed) - A Decart API key from platform.decart.ai
- A physical iOS or Android device (WebRTC requires real camera hardware)
Project setup
1
Clone the repository
2
Install dependencies
3
Add your API key
Create a
.env file in the project root:4
Run on a device
LiveKit setup for React Native
React Native realtime runs on LiveKit’s React Native packages. Plainreact-native-webrtc is not supported. The cloned example already wires this up — here’s what makes it work.
1. Install the LiveKit packages (already in the example’s package.json):
registerGlobals() must run before your router or root component is imported:
app.json:
npx expo prebuild and rebuild the native app. LiveKit does not run in Expo Go — you need a development build on a physical device.
Using bare React Native (CLI) instead of Expo? The realtime code is identical, but you need two Metro/Babel config changes (Expo applies them for you). See React Native CLI (bare projects).
Architecture overview
The app uses Expo Router with three screens:
Key directories:
Core integration: the WebRTC hook
TheuseWebRTC hook handles the entire Decart SDK lifecycle — creating a client, capturing the camera, connecting to a realtime model, and managing cleanup.
-
Model constraints drive camera setup. Each model defines its own
fps,width, andheight. Resolve the FPS withresolveFpsNumber(model.fps)(it can be a descriptor object rather than a plain number), andgetMediaStreamrequests exactly those dimensions from LiveKit’smediaDevices. -
preferredVideoCodec: "vp8"selects the codec. React Native works best with VP8. The SDK negotiates it for you — there’s no manual SDP rewriting (the oldcustomizeOffer/forceCodecInSDPapproach is gone). -
No
MediaStreamcasts.connect<MediaStream>(...)takes LiveKit’s React NativeMediaStreamtype as a generic, so you pass the captured stream straight through — noas unknown asbridging.
Switching models
Each model offers different creative capabilities:
Switch models by creating a new
ModelDefinition and reconnecting:
Applying style presets
The app includes curated prompt presets for each model. When a user selects a preset, the prompt is sent to the active model:set() instead of setPrompt() to include a reference image:
Handling app lifecycle
Mobile apps frequently move between foreground and background. The example handles this by disconnecting when backgrounded and reconnecting when foregrounded:Camera controls
Mirror the front camera. The SDK’smirror option is browser-only and is rejected on React Native with UNSUPPORTED_PLATFORM_FEATURE. Mirror the local preview at the view layer instead — pass mirror to LiveKit’s <RTCView>:
Adding Lucy 2.1 support
Adding Lucy 2.1 requires two small changes: updating the model selector and using the
set() method for character reference images.set() for character reference images:
Lucy 2.1’s key feature is character transformation via reference images. Replace setPrompt() with set() when a reference image is available:
Next steps
Lucy 2.1 guide
Character transformation, reference images, and the
set() API in detail.JavaScript SDK
Full SDK reference for connection management, events, and client-side auth.
All realtime models
Compare Lucy 2.1 and Lucy Restyle Live side by side.
Source code
Clone the full example app and start building.