embedding unity in react native
date: March 8, 2021
Problem: Building a construction app that needs both a polished mobile experience and complex AR visualization. React Native excels at navigation, authentication, and project management. Unity excels at BIM rendering and AR. The standard approach is two separate apps with deep links. Poor UX. Fragmented experience.
Solution: Embed Unity as a framework inside the React Native app. Single binary. Unity loads on demand only when users enter AR mode, keeping launch time fast.
Architecture
React Native is the primary app shell. Unity is the AR engine, loaded as an embedded framework that activates only when users enter AR mode. The native bridge is an Objective-C layer enabling bidirectional communication.
Unity compiles to an iOS framework rather than a standalone application. The generated UnityFramework.framework drops into the React Native Xcode workspace. Unity loads at runtime only when needed.
Native call proxy
The bridge uses Objective-C protocols. Unity defines what it needs from the host. React Native implements the protocol.
@protocol NativeCallsProtocol
@required
- (void) requestPreferences;
- (void) requestModel;
- (void) requestMarker;
- (void) logMessageReceived:(char*)stackTrace;
@end
extern "C" {
void requestPreferences() { return [api requestPreferences]; }
void requestModel() { return [api requestModel]; }
void requestMarker() { return [api requestMarker]; }
void logMessageReceived(char* stackTrace) {
return [api logMessageReceived:stackTrace];
}
}
Clean interface with no direct coupling between runtimes. Unity doesn't know React Native exists. It just calls functions.
On the Unity side, a NativeAPI class wraps the external calls using P/Invoke. The HOSTMANAGER compiler directive controls this code path: development builds run standalone with direct API access, production builds communicate through the bridge.
#if HOSTMANAGER
public class NativeAPI
{
[DllImport("__Internal")]
public static extern void requestPreferences();
[DllImport("__Internal")]
public static extern void requestModel();
[DllImport("__Internal")]
public static extern void logMessageReceived(string stackTrace);
}
#endif
Host manager pattern
The HostManager singleton coordinates all host application interaction. On startup, it requests preferences from React Native to get auth tokens and project configuration, then waits for model data.
public class HostManager : Singleton<HostManager>
{
public string ModelResponseString { get; private set; }
public void RequestModelData()
{
ModelResponseString = null;
NativeAPI.requestModel();
}
public void ResponseRequestModel(string data)
{
ModelResponseString = data;
}
}
React Native owns authentication and project selection. Unity receives tokens ready to use. No duplicate auth logic.
Model import integration
The importer waits for React Native to provide model data using Unity's coroutine system:
private IEnumerator WaitForReactCoroutine(Transform parent,
Action<ModelController> onModelImported)
{
HostManager.Instance.RequestModelData();
yield return new WaitUntil(() =>
HostManager.Instance.ModelResponseString != null);
onModelImported(BimFactory.ImportBimJsonModel(
parent, HostManager.Instance.ModelResponseString));
}
This keeps Unity responsive during the wait. Loading animations continue. UI remains interactive.
Viewer type selection
React Native determines the viewing mode through the settings JSON. Two viewer types: MarkerAR for full AR mode with QR marker anchoring and camera-feed overlay, Mobile3D for a standard 3D viewer with pan, zoom, and rotate. React Native controls which mode launches. Unity loads the appropriate scene.
Error handling
Unity crashes shouldn't crash the entire app. The bridge catches unhandled exceptions and Unity log messages of type Exception, forwarding them to React Native. React Native receives crash logs, can display user-friendly error messages, and can report to crash analytics. Unity's problems don't leave users stranded in a broken AR view.
Why this architecture
Users don't switch between apps. They transition from project list to AR view within a single experience. Login once in React Native, token flows to Unity. Update the React Native UI without touching Unity. Update Unity rendering without touching React Native. Independent development cycles.
Not a web view. Not a cross-compiled shim. Real native code on both sides.Result: A construction app that feels native everywhere. Smooth navigation in React Native, full AR in Unity, shipped as a single binary.