# How to Add AR Previews to Your App — Copy-Paste Prompts Inside

- Tool: App Features
- Last updated: July 2026

## TL;DR

Augmented reality previews overlay a 3D model or image on the user's live camera feed so they can place a product in their real environment before buying. The feature needs ARKit (iOS) or ARCore (Android) via ar_flutter_plugin, .usdz and .glb model files in Supabase Storage, camera permission handling, and a screenshot export. Expect 2–4 days with FlutterFlow plus manual AR plugin wiring; web-only tools like Lovable and V0 cannot deliver true AR — WebXR on iOS Safari is too limited in 2026.

## What an Augmented Reality Preview Feature Actually Is

An AR preview feature places a 3D model of your product into the user's physical space through their phone camera — a furniture retailer lets shoppers see a sofa in their living room; a cosmetics brand shows lipstick shades on the user's face. The core technology is ARKit on iOS (16+) and ARCore on Android (9+), both accessed through the Flutter ar_flutter_plugin. The feature's three hard problems are: sourcing correctly formatted 3D models (.usdz for iOS, .glb for Android), handling camera permission denial gracefully, and compositing the camera feed with the Flutter overlay correctly in a screenshot. Web-based tools (Lovable, V0) cannot deliver true AR — the WebXR Device API lacks ARKit plane detection on iOS as of 2026. This is a mobile-native feature.

## Anatomy of the Feature

Six components. AR session management and camera permission handling are where first builds stall. The model format pipeline is a parallel workstream that must be planned before development begins.

- **AR session manager** (backend): Initializes and manages the ARKit (iOS) or ARCore (Android) session. In Flutter, the ar_flutter_plugin package wraps both native frameworks behind a unified Dart API. The session handles plane detection, hit-testing (finding where the user taps on a detected surface), and anchor placement (pinning the 3D model to a world coordinate).
- **3D model renderer** (service): Renders the 3D model anchored to the detected surface. iOS uses SceneKit with .usdz files (the format Apple mandates for Quick Look AR). Android uses ARCore's SceneViewer with .glb or .gltf files. Model files are stored in Supabase Storage and fetched by URL at session start. For web fallback: Google's Model Viewer web component provides 3D rotation (not true AR) in a browser.
- **Camera permission layer** (ui): Requests camera access using Flutter's permission_handler package. Must handle three states: granted (start AR session), denied (show explainer with 'Try again' button), and permanentlyDenied (show dialog explaining how to enable camera in Settings, then call openAppSettings() to deep-link to device Settings).
- **Surface / face tracker** (backend): Detects flat horizontal or vertical planes (ARKit Plane Detection, ARCore Plane Discovery) for furniture-style AR, or tracks facial landmarks (Apple Vision framework, ARKit Face Tracking) for cosmetics try-on. An animated ring overlay on the detected floor surface indicates to the user where the model will be placed.
- **Capture and share** (ui): Saves the AR view as a PNG to the device photo library. Flutter's RenderRepaintBoundary.toImage() does NOT capture the camera texture (it is an external texture not in the Flutter widget tree) — requires platform-specific capture: ARSCNView.snapshot() on iOS, Frame.acquireCameraImage() composite on Android, or the screen_capturer package for a native screen buffer capture.
- **Model asset store** (data): Supabase Storage bucket holding .usdz and .glb model files (1–20 MB each). A products table with ar_assets rows links each product to its platform-specific model URLs. scale_factor stores the initial display scale so a chair renders at chair-size, not doll-size or building-size.

## Data model

Two tables link products to their platform-specific AR models; run this in the Supabase SQL editor:

```sql
-- Products table (extend if you already have one)
create table if not exists public.products (
  id uuid primary key default gen_random_uuid(),
  name text not null,
  description text,
  thumbnail_url text,
  created_at timestamptz not null default now()
);

-- AR assets linked to products
create table public.ar_assets (
  id uuid primary key default gen_random_uuid(),
  product_id uuid references public.products(id) on delete cascade not null,
  platform text not null check (platform in ('ios', 'android', 'web')),
  model_url text not null,
  thumbnail_url text,
  scale_factor float not null default 1.0,
  created_at timestamptz not null default now()
);

alter table public.ar_assets enable row level security;
alter table public.products enable row level security;

-- Public read for product catalog (adjust if catalog is private)
create policy "Anyone can view products"
  on public.products for select
  using (true);

create policy "Anyone can view AR assets"
  on public.ar_assets for select
  using (true);

-- Admins only for insert/update (assuming admin role or service key)
-- Add admin-specific policies here if needed

create index ar_assets_product_platform_idx
  on public.ar_assets (product_id, platform);
```

The composite index on (product_id, platform) makes the per-device asset lookup fast — the app queries WHERE product_id = ? AND platform = 'ios' to get the right model format. Store model_url as the Supabase Storage path (not the full signed URL) and generate signed URLs at request time for private assets.

## Build paths

### Lovable — fit 1/10, not recommended

Lovable is a Vite/React web builder. True AR (ARKit/ARCore plane detection and spatial anchoring) requires native device APIs that are completely inaccessible from a browser SPA. WebXR on iOS Safari lacks ARKit plane detection as of 2026.

1. If a web-only AR experience is acceptable, embed Google's Model Viewer web component (<model-viewer> tag) in a Lovable page — it provides 3D rotation and basic AR on Android Chrome via WebXR, but NOT true surface detection or spatial placement on iOS
2. Add the product's .glb file URL as the src attribute and the .usdz file as the ios-src attribute — iOS users get Apple Quick Look (a native AR viewer launched from Safari), which is a redirect from the browser, not embedded AR
3. This approach is limited to 3D rotation on desktop/iOS and basic WebXR AR on Android Chrome — communicate this clearly to stakeholders before building

Starter prompt:

```
Add a 3D product viewer to the product detail page using Google Model Viewer. Install the @google/model-viewer package. Component: ArProductViewer — renders a <model-viewer> element with src set to the product's .glb URL and ios-src set to the .usdz URL. Enable ar, ar-modes='webxr scene-viewer quick-look', camera-controls, and auto-rotate. Show a 'View in your space' button that triggers the model-viewer's activateAR() method. Display the product thumbnail as the poster image while the 3D model loads. Handle loading state with a spinner overlay. On small screens show the viewer full-width at 100vw height.
```

Limitations:

- Not true AR — no ARKit plane detection, no spatial anchoring, no surface tracking
- iOS users get Apple Quick Look (a system-level viewer) instead of in-app AR — the experience leaves your app
- WebXR AR on Android Chrome works for basic object placement but has no face tracking and limited plane detection quality compared to native ARCore

### Flutterflow — fit 3/10, 2–4 days

FlutterFlow can host the product list UI, camera permission flow, and capture button visually, while the AR session itself is wired via a Custom Action using ar_flutter_plugin. Requires FlutterFlow Pro for Custom Actions.

1. Add ar_flutter_plugin as a custom package dependency in FlutterFlow (Project Settings → Custom Code → Pub.dev packages): ar_flutter_plugin, permission_handler, share_plus, screen_capturer
2. Create a Custom Action named InitializeARSession that initializes the ARKitController (iOS) or ARCoreController (Android) and handles plane detection — this Custom Action contains Dart code you write or have written for you
3. In FlutterFlow, build the product detail page visually: product image, name, 'Try in AR' button. Wire the button to a permission check action: if camera granted → navigate to AR page; if denied → show dialog; if permanentlyDenied → call openAppSettings()
4. On the AR page, add a Custom Widget wrapping the ARView widget from ar_flutter_plugin; add capture and share buttons as standard FlutterFlow widgets wired to Custom Actions for screenshot capture
5. Test on a real device via FlutterFlow's Test on Device (download APK via Android, or TestFlight for iOS) — AR never works in FlutterFlow's browser preview

Limitations:

- AR plugin wiring requires Custom Action Dart code — founders without Flutter experience will need this written for them
- FlutterFlow Pro ($70/mo) is required for Custom Actions and Custom Widgets
- No AR functionality in FlutterFlow's browser preview — all AR testing must happen on a real physical device
- The 3D model pipeline (.usdz/.glb creation and validation) is a separate workstream entirely outside FlutterFlow

### V0 — fit 1/10, not recommended

V0 generates Next.js web apps. WebXR on mobile Safari (iOS) lacks ARKit plane detection in 2026. V0 can embed Google Model Viewer for 3D rotation but cannot deliver true spatial AR on iOS.

1. Use V0 to build a product detail page with an embedded Model Viewer component for 3D rotation — follow the same steps as the Lovable path above with the @google/model-viewer package
2. Deploy to Vercel; Android Chrome users get WebXR-based basic AR placement; iOS users get an Apple Quick Look redirect from the <model-viewer> ios-src attribute

Starter prompt:

```
Create a Next.js product detail page with a 3D AR viewer using @google/model-viewer. Component: ProductArViewer (client component). Props: glbUrl (string), usdz Url (string), posterUrl (string), productName (string). Render a model-viewer element with ar enabled, ar-modes set to 'webxr scene-viewer quick-look', camera-controls, auto-rotate, and shadow-intensity='1'. Set poster to posterUrl while loading. Add a 'View in your space' button below the viewer that calls the model-viewer element's activateAR() method via a ref. Show a loading skeleton while the 3D model downloads. On screens smaller than 768px, render the viewer at full viewport width and 60vh height. Fetch product data from Supabase ar_assets table filtered by product_id and platform (pass 'android' for glb, 'ios' for usdz).
```

Limitations:

- Not true AR on iOS — no spatial placement, no surface detection, no ARKit features
- WebXR on iOS Safari is too limited for production AR experiences in 2026
- Android WebXR AR placement works for basic use cases but lacks face tracking and fine-grained anchor control

### Custom — fit 5/10, 3–6 weeks

Full native stack: Swift/ARKit (iOS) + Kotlin/ARCore (Android), or a complete Flutter app with ar_flutter_plugin and full Dart code. The right path when AR quality and feature depth (face tracking, multi-user anchors, model optimization) directly affect product value.

1. Native Swift + ARKit (iOS) and Kotlin + ARCore (Android), or Flutter with ar_flutter_plugin and complete Dart code — full access to every ARKit and ARCore API without FlutterFlow abstraction layers
2. 3D model pipeline: product photography → photogrammetry tool (Polycam, RealityCapture, or Poly.cam) or 3D artist → .usdz conversion via Apple's Reality Converter + .glb validation via Google's gltf-validator → Draco compression for bandwidth optimization → Supabase Storage
3. Server-side model optimization via a Supabase Edge Function: validate model formats on upload, reject files with unsupported material types before they reach the asset library
4. Advanced features: Apple Vision framework for face tracking (cosmetics try-on), ARCore Cloud Anchors for multi-user shared AR sessions, ARKit's LiDAR-based occlusion on Pro iPhones for photorealistic model placement behind furniture

Limitations:

- ARKit/ARCore expertise is required — this is not a generalist web development skill
- 3D model asset pipeline (photography, modeling, format conversion, optimization) is a separate workstream that can add weeks and budget to the project
- iOS App Store requires a full app submission; TestFlight for beta testing — the AR experience cannot be previewed in a browser

## Gotchas

- **AR session never starts in FlutterFlow Test Mode** — FlutterFlow's in-browser preview runs in a web environment that has no access to native ARKit or ARCore APIs. When ar_flutter_plugin tries to initialize the AR session in this environment, the call fails silently — the camera feed never appears and the AR page looks completely broken. This is not a bug in your implementation. Fix: All AR testing must happen on a real physical device. In FlutterFlow, use Test on Device (download and install the debug APK on Android, or use TestFlight for iOS) to get a native build on your phone. Never demo the AR feature in the FlutterFlow browser preview — it will always appear broken.
- **Camera permission denied permanently with no recovery path** — AI tools and most scaffolded code make a single camera permission request. On iOS and Android, if the user taps 'Deny' once (or 'Don't Ask Again' on Android), all future permission requests are silently rejected without prompting the user again. The AR page appears to hang or shows a black camera feed with no explanation. Fix: Use permission_handler to check PermissionStatus after every request. On permanentlyDenied, call openAppSettings() which deep-links to the device's Settings app where the user can manually enable camera access. Show a clear dialog before this redirect: 'Camera access is required for AR previews. Tap Open Settings to enable it.' Specify all three states (granted, denied, permanentlyDenied) in your prompt.
- **.usdz loads on iOS but .glb shows a black box on Android** — ARCore requires .glb files using the glTF 2.0 specification with PBR metallic-roughness materials. 3D models exported from common tools (Blender, Sketchfab) may use unsupported material types (KHR_materials_unlit, or non-standard extensions) that ARCore cannot render — resulting in a black or invisible model at placement time. Fix: Validate every .glb file with Google's gltf-validator (available as a web tool at khronos.github.io/glTF-Validator/) before uploading to Supabase Storage. Export from Blender using 'Format: glTF 2.0', 'Materials: PBR Metallic Roughness' settings. Test on a real Android 10+ device — the ARCore viewer behaves differently from desktop WebGL previews.
- **Plane detection anchor drifts on low-texture floors** — ARKit and ARCore use visual feature points from the camera feed to detect flat surfaces. On plain white floors, solid-color carpets, or highly reflective (glossy) surfaces, there are too few visual features for reliable plane detection. The detected plane drifts as the device moves, causing the placed model to slide around instead of staying fixed. Fix: Add a hint overlay that appears when plane detection confidence is below a threshold: 'Point your camera at a patterned surface for better AR tracking.' Use ARKit's worldAlignment = .gravityAndHeading for more stable anchors on iOS. Display the animated ring indicator only when a plane is actively detected, not continuously — this sets correct user expectations about when placement is reliable.
- **Screenshot captures a black frame instead of the AR composite** — Flutter's RenderRepaintBoundary.toImage() captures only the Flutter widget layer overlay (buttons, UI chrome) and not the camera texture, which is rendered as a native platform view (an 'external texture') outside the Flutter widget tree. The resulting screenshot is a black background with only the AR UI buttons visible. Fix: Use the screen_capturer Flutter package which captures the native screen buffer including the camera texture. On iOS, alternatively use ARSCNView.snapshot() called via a Method Channel. On Android, use MediaProjection or Frame.acquireCameraImage() and composite manually. Specify 'use screen_capturer package for screenshot, not RenderRepaintBoundary' in your Custom Action prompt.

## Best practices

- Validate every .glb file with Google's gltf-validator before uploading to Supabase Storage — invalid material types cause invisible models on Android without any error message to the user
- Handle all three camera permission states explicitly: granted (start AR), denied (show rationale + try again button), permanentlyDenied (show Settings redirect dialog) — a missing permanentlyDenied handler silently breaks the feature for returning users who already denied once
- Store model_url as a Supabase Storage path (not a full signed URL) and generate signed URLs on demand — signed URLs expire, and hardcoded URLs break when bucket configuration changes
- Add a scale_factor column to your ar_assets table and apply it on model load — models without calibrated scale often appear the size of a toy or a building, ruining the spatial preview experience
- Show a surface detection ring animation on the floor before allowing model placement — this teaches users to move their phone slowly across the room and dramatically reduces 'the AR doesn't work' support requests
- Add a 'How to use AR' tooltip overlay on first launch (shown only once, dismissed by tap) explaining that the user should point at a flat surface — first-time AR users consistently miss this step
- Use Draco compression on .glb files (reducible to 10–30% of original size) before uploading to Storage — a 20 MB model on a slow connection produces a loading bar long enough for users to abandon the feature
- Always ship a static product image fallback for devices that fail the ARCore/ARKit support check or deny camera permission — AR-capable devices are still not universal

## Frequently asked questions

### Does AR work on all Android phones or only newer ones?

ARCore requires Android 9+ (Pie) and a device on Google's ARCore supported devices list. As of 2026, the vast majority of actively used Android phones qualify — but very low-end devices (under $100) and older flagship models from 2017 and earlier may not. Use ARCoreApk.checkAvailability() at app launch to detect support and route unsupported users to the 2D fallback.

### Do I need a 3D artist to create the models, or can I convert product photos automatically?

Photogrammetry tools like Poly.cam, Polycam, and RealityCapture can generate a rough 3D model from 20–40 product photos taken from different angles — no 3D artist needed for simple objects. Complex items (transparent glass, highly reflective surfaces, very thin parts) still need a 3D modeler. AI text-to-3D tools (Meshy.ai) can generate plausible models from a text description for prototyping, though output quality varies significantly. Budget for manual cleanup by a 3D artist for production quality.

### Will AR previews work in poor lighting conditions?

ARKit and ARCore both degrade in very dark environments — plane detection and tracking require visible textures and features in the camera feed. Below roughly 50 lux (dim indoor lighting) you will see increased anchor drift and failed plane detection. Add lighting hints in your surface detection guidance UI: 'For best results, use in a well-lit room.' ARKit includes an ambient light estimate you can read to show a warning when lighting is too low.

### Can users share their AR screenshot directly to Instagram or TikTok?

Yes. Capture the screen using the screen_capturer Flutter package (which includes the camera texture), then trigger Share Plus's share() method with the file path. On iOS and Android, this opens the native share sheet where users can choose Instagram, TikTok, Messages, or any installed app as the target. Some apps (notably Instagram Stories) support direct sharing via their URI schemes — wire these separately if you want one-tap share to a specific platform.

### How large are the 3D model files and will they slow down my app?

Raw .glb exports from Blender or Sketchfab are typically 5–50 MB per model. Apply Draco mesh compression (integrated into Blender's glTF exporter and the gltf-pipeline tool) to reduce this to 1–5 MB per model. Show a download progress bar when models load over cellular — a 20 MB model on a slow 4G connection takes 5–15 seconds and users will assume the feature is broken without a visible indicator.

### Is ARKit only for iOS — what do Android users get?

ARKit is Apple-only (iOS 16+ and iPadOS). Android users get ARCore, which provides comparable functionality: plane detection, hit testing, anchor placement, and light estimation. Both are accessed through Flutter's ar_flutter_plugin with a unified API. The only difference your app needs to handle is the model format: .usdz for iOS, .glb for Android. Store both in Supabase Storage and select the correct one based on Platform.isIOS at runtime.

### Can the AR preview show a product in multiple colors without loading a new model?

Yes, for simple color swaps. ARKit and ARCore both support material property changes at runtime — you can update the albedo color of a material on an already-placed model without reloading the entire .usdz or .glb file. This requires native code or a Flutter method channel call to the AR session, specifying the material index and the new color value. It is not supported out of the box in ar_flutter_plugin — plan it as a custom extension if color switching is a product requirement.

### How accurate is the scale — will furniture AR previews be true-to-size?

On standard phones, ARKit and ARCore achieve ±5–10% scale accuracy on flat surfaces, which is sufficient for most furniture previews. On iPhone Pro models with a LiDAR scanner (iPhone 12 Pro and later), ARKit uses depth data to improve both scale accuracy and placement speed significantly — LiDAR phones detect surfaces 4–5× faster and achieve ±2–3% scale accuracy. If true-to-scale accuracy is a product promise, target LiDAR-equipped iPhones as your primary device tier and communicate this to users.

---

Source: https://www.rapidevelopers.com/app-features/augmented-reality-previews
© RapidDev — https://www.rapidevelopers.com/app-features/augmented-reality-previews
