Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Create a Virtual Reality VR Experience in FlutterFlow

FlutterFlow has no native VR headset support — Flutter does not connect to Oculus, Vision Pro, or SteamVR. What you can build is compelling mobile VR: 360° panorama photos with gyroscope-driven navigation, 3D model viewers, and Google Cardboard split-screen mode. These work well for virtual tours, 360° product showcases, and real estate previews.

What you'll learn

  • What VR capabilities Flutter and FlutterFlow actually support
  • Adding 360° panorama photos with gyroscope navigation using panorama_viewer
  • Displaying interactive 3D models with flutter_3d_controller
  • Enabling Google Cardboard split-screen stereoscopic mode
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read35-50 minFlutterFlow Pro+ (Custom Widgets required for panorama and 3D packages)March 2026RapidDev Engineering Team
TL;DR

FlutterFlow has no native VR headset support — Flutter does not connect to Oculus, Vision Pro, or SteamVR. What you can build is compelling mobile VR: 360° panorama photos with gyroscope-driven navigation, 3D model viewers, and Google Cardboard split-screen mode. These work well for virtual tours, 360° product showcases, and real estate previews.

Flutter VR: what's real and what's not

Before building, it's important to be clear about Flutter's VR capabilities. Flutter does not support VR headset runtimes. There is no Oculus SDK, no Apple Vision Pro visionOS support, no SteamVR integration. Flutter renders to a 2D canvas — it's a mobile UI framework, not a 3D game engine. What Flutter does support is mobile VR experiences: 360° equirectangular photos displayed on a sphere with gyroscope and touch navigation (the same experience as Google Street View), 3D model viewers using WebGL-backed packages, and Google Cardboard's simple left-right split-screen format. For most business use cases — virtual property tours, 360° product photography, immersive training content — this is entirely sufficient and deployable to iOS and Android without any headset.

Prerequisites

  • FlutterFlow Pro plan (Custom Widgets required for panorama and 3D packages)
  • A 360° equirectangular image (available free on sites like polyhaven.com for testing)
  • Basic understanding of FlutterFlow Custom Widgets
  • Physical iOS or Android device for testing — gyroscope features don't work in web browser

Step-by-step guide

1

Add the panorama_viewer package to your FlutterFlow project

In FlutterFlow, go to Settings → Pubspec Dependencies → Add Dependency. Search for 'panorama_viewer' and add the latest version (1.x+). Also add 'sensors_plus' to enable gyroscope-driven navigation. Click Save. These packages enable 360° photo viewing with touch drag and automatic tilting when the user moves their phone. Next, upload your 360° equirectangular image to Firebase Storage or use a public URL. A standard 360° photo is a 2:1 aspect ratio image — if you photograph with a standard camera it won't work. Use a 360° camera or download test images from polyhaven.com.

Expected result: panorama_viewer and sensors_plus packages added in Settings. A 360° test image URL ready to use.

2

Create a PanoramaViewer Custom Widget

Go to Custom Code → Custom Widgets → Add Widget. Name it 'PanoramaViewer360'. This widget fills a given area and renders a 360° equirectangular image on a sphere that the user can navigate by dragging or by moving their phone. Set parameters: imageUrl (String), enableSensors (boolean, default true), and initialHorizontalAngle (double, default 0.0). Paste the code below. After saving, drag the widget onto any page and set the imageUrl parameter to your 360° image URL.

panorama_viewer_widget.dart
1// Custom Widget: PanoramaViewer360
2// Package: panorama_viewer ^1.0.0
3import 'package:flutter/material.dart';
4import 'package:panorama_viewer/panorama_viewer.dart';
5
6class PanoramaViewer360 extends StatelessWidget {
7 const PanoramaViewer360({
8 super.key,
9 required this.imageUrl,
10 this.enableSensors = true,
11 this.initialLongitude = 0.0,
12 this.initialLatitude = 0.0,
13 this.sensitivity = 1.0,
14 this.width = double.infinity,
15 this.height = 300.0,
16 });
17
18 final String imageUrl;
19 final bool enableSensors;
20 final double initialLongitude;
21 final double initialLatitude;
22 final double sensitivity;
23 final double width;
24 final double height;
25
26 @override
27 Widget build(BuildContext context) {
28 return SizedBox(
29 width: width,
30 height: height,
31 child: PanoramaViewer(
32 sensitivity: sensitivity,
33 sensorControl: enableSensors
34 ? SensorControl.absoluteOrientation
35 : SensorControl.none,
36 longitude: initialLongitude,
37 latitude: initialLatitude,
38 child: Image.network(
39 imageUrl,
40 fit: BoxFit.cover,
41 loadingBuilder: (context, child, loadingProgress) {
42 if (loadingProgress == null) return child;
43 return Center(
44 child: CircularProgressIndicator(
45 value: loadingProgress.expectedTotalBytes != null
46 ? loadingProgress.cumulativeBytesLoaded /
47 loadingProgress.expectedTotalBytes!
48 : null,
49 ),
50 );
51 },
52 ),
53 ),
54 );
55 }
56}

Expected result: A 360° interactive panorama renders on the page. Touch-dragging pans the view. On a physical device, tilting the phone also pans the view.

3

Add a 3D model viewer with flutter_3d_controller

For displaying interactive 3D product models or virtual objects, add the flutter_3d_controller package. This package renders .glb (binary glTF) and .obj files with WebGL acceleration. Add it in Settings → Pubspec Dependencies. Create a Custom Widget named 'Model3DViewer'. The widget renders a 3D scene where the user can rotate and zoom the model by dragging and pinching. Upload your .glb file to Firebase Storage. Good sources for free 3D models: sketchfab.com (download as .glb) and Google's Poly archive.

model_3d_viewer_widget.dart
1// Custom Widget: Model3DViewer
2// Package: flutter_3d_controller ^1.3.0
3import 'package:flutter/material.dart';
4import 'package:flutter_3d_controller/flutter_3d_controller.dart';
5
6class Model3DViewer extends StatefulWidget {
7 const Model3DViewer({
8 super.key,
9 required this.modelUrl,
10 this.backgroundColor,
11 this.autoPlay = true,
12 this.width = double.infinity,
13 this.height = 300.0,
14 });
15
16 final String modelUrl;
17 final Color? backgroundColor;
18 final bool autoPlay;
19 final double width;
20 final double height;
21
22 @override
23 State<Model3DViewer> createState() => _Model3DViewerState();
24}
25
26class _Model3DViewerState extends State<Model3DViewer> {
27 final Flutter3DController _controller = Flutter3DController();
28
29 @override
30 Widget build(BuildContext context) {
31 return SizedBox(
32 width: widget.width,
33 height: widget.height,
34 child: Flutter3DViewer(
35 src: widget.modelUrl,
36 controller: _controller,
37 progressBarColor: Colors.blue,
38 onLoad: () {
39 if (widget.autoPlay) _controller.playAnimation();
40 },
41 onError: (error) {
42 debugPrint('3D model error: $error');
43 },
44 ),
45 );
46 }
47}

Expected result: A 3D model renders in the widget area. The user can drag to rotate and pinch to zoom. If the model has animation, it plays automatically.

4

Enable Google Cardboard split-screen mode

Google Cardboard works by displaying two side-by-side views of the same scene with a slight offset — this creates the stereoscopic 3D illusion when viewed through the Cardboard lenses. For a panorama viewer, this means rendering the same 360° scene twice with slightly different viewpoints. Add the google_cardboard package or implement a simple split-screen layout manually: a Row widget with two Expanded children, each containing a PanoramaViewer360 widget with the same imageUrl. Set the rotation offset on the right viewer to a small positive value (3-5 degrees) relative to the left viewer using a shared App State variable tracking the current pan angle.

Expected result: Two side-by-side panorama views display on screen. When placed in a Google Cardboard viewer, the scene appears 3D.

5

Build a virtual tour with multiple panorama hotspots

A virtual tour links multiple 360° panoramas together through interactive hotspots — tapping an arrow in the panorama loads the next room's panorama. Create an App State variable 'currentPanoramaUrl' (String). Stack the PanoramaViewer360 widget with a Row of IconButton widgets positioned at the panorama navigation points. Each button updates currentPanoramaUrl to the next room's image URL. Wrap the buttons in Positioned widgets inside the Stack to place them at specific screen locations. Add a fade transition animation on the PanoramaViewer360 widget when currentPanoramaUrl changes for a smooth room transition.

Expected result: A multi-room virtual tour where tapping navigation arrows smoothly transitions between panoramic views of different rooms.

Complete working example

panorama_tour_widget.dart
1// FlutterFlow Custom Widget: PanoramaTour
2// A virtual tour with multiple connected panoramas and hotspot navigation
3// Package: panorama_viewer ^1.0.0
4import 'package:flutter/material.dart';
5import 'package:panorama_viewer/panorama_viewer.dart';
6
7class PanoramaTour extends StatefulWidget {
8 const PanoramaTour({
9 super.key,
10 required this.scenes, // List of {url, label} maps
11 this.initialIndex = 0,
12 this.height = 400.0,
13 });
14
15 final List<dynamic> scenes;
16 final int initialIndex;
17 final double height;
18
19 @override
20 State<PanoramaTour> createState() => _PanoramaTourState();
21}
22
23class _PanoramaTourState extends State<PanoramaTour>
24 with SingleTickerProviderStateMixin {
25 late int _currentIndex;
26 late AnimationController _fadeController;
27 late Animation<double> _fadeAnimation;
28
29 @override
30 void initState() {
31 super.initState();
32 _currentIndex = widget.initialIndex;
33 _fadeController = AnimationController(
34 vsync: this,
35 duration: const Duration(milliseconds: 400),
36 );
37 _fadeAnimation = CurvedAnimation(
38 parent: _fadeController,
39 curve: Curves.easeInOut,
40 );
41 _fadeController.value = 1.0;
42 }
43
44 @override
45 void dispose() {
46 _fadeController.dispose();
47 super.dispose();
48 }
49
50 Future<void> _navigateTo(int index) async {
51 if (index < 0 || index >= widget.scenes.length) return;
52 await _fadeController.reverse();
53 setState(() => _currentIndex = index);
54 await _fadeController.forward();
55 }
56
57 @override
58 Widget build(BuildContext context) {
59 final scene = widget.scenes[_currentIndex];
60 final hasPrev = _currentIndex > 0;
61 final hasNext = _currentIndex < widget.scenes.length - 1;
62
63 return SizedBox(
64 height: widget.height,
65 child: Stack(
66 children: [
67 FadeTransition(
68 opacity: _fadeAnimation,
69 child: PanoramaViewer(
70 sensorControl: SensorControl.absoluteOrientation,
71 child: Image.network(
72 scene['url'] as String,
73 fit: BoxFit.cover,
74 ),
75 ),
76 ),
77 // Room label
78 Positioned(
79 top: 16, left: 16,
80 child: Container(
81 padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
82 decoration: BoxDecoration(
83 color: Colors.black54,
84 borderRadius: BorderRadius.circular(20),
85 ),
86 child: Text(
87 scene['label'] as String? ?? '',
88 style: const TextStyle(color: Colors.white, fontSize: 14),
89 ),
90 ),
91 ),
92 // Navigation arrows
93 Positioned(
94 bottom: 24,
95 left: 0, right: 0,
96 child: Row(
97 mainAxisAlignment: MainAxisAlignment.center,
98 children: [
99 if (hasPrev)
100 FloatingActionButton.small(
101 heroTag: 'prev',
102 onPressed: () => _navigateTo(_currentIndex - 1),
103 backgroundColor: Colors.black54,
104 child: const Icon(Icons.arrow_back, color: Colors.white),
105 ),
106 const SizedBox(width: 16),
107 if (hasNext)
108 FloatingActionButton.small(
109 heroTag: 'next',
110 onPressed: () => _navigateTo(_currentIndex + 1),
111 backgroundColor: Colors.black54,
112 child: const Icon(Icons.arrow_forward, color: Colors.white),
113 ),
114 ],
115 ),
116 ),
117 ],
118 ),
119 );
120 }
121}

Common mistakes when creating a Virtual Reality VR Experience in FlutterFlow

Why it's a problem: Promising clients true VR headset support when building with FlutterFlow

How to avoid: Be explicit: FlutterFlow delivers mobile VR (360° views, Google Cardboard) not immersive headset VR. For true headset VR, clients need Unity or Unreal Engine, which is a separate and significantly more complex project.

Why it's a problem: Testing panorama gyroscope navigation in the web browser

How to avoid: Always test panorama and VR features on a physical device. Use the QR code in Run Mode to open the preview on your phone and test the gyroscope tilting behavior.

Why it's a problem: Using regular photos instead of 360° equirectangular images

How to avoid: Only use images explicitly formatted as equirectangular 360° panoramas. Download test images from polyhaven.com or shoot with a 360° camera app.

Best practices

  • Be upfront with clients that Flutter VR means mobile VR (360°/Cardboard), not headset VR
  • Compress 360° images to under 3MB before uploading — large images cause slow load and out-of-memory crashes on older phones
  • Always provide a 2D fallback view (regular photo) for users on web or devices without gyroscopes
  • Store panorama scene data in Firestore to update tours without app updates
  • Use loading indicators while panorama images download — a blank screen while loading frustrates users
  • Test on a mid-range Android device (not just the latest iPhone) — 3D rendering performance varies significantly
  • For commercial virtual tours, consider dedicated 360° tour platforms (Matterport, Kuula) that handle hosting and analytics

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building a virtual property tour in FlutterFlow using panorama_viewer. I have 5 equirectangular 360° images stored in Firebase Storage. I want a Flutter Custom Widget that displays the current panorama with gyroscope navigation, and shows navigation buttons to move to the next/previous room with a fade transition between rooms. The widget should accept a list of scene objects (each with url and roomName) and the initial room index.

FlutterFlow Prompt

In FlutterFlow, I want to create a virtual product showcase that shows a 3D model (.glb file) that users can rotate and zoom. The model URL is stored in a Firestore document field called 'modelUrl'. How do I set up the flutter_3d_controller Custom Widget, pass the Firestore field value as a parameter, and display it on a product detail page?

Frequently asked questions

Does FlutterFlow support Oculus or Meta Quest headsets?

No. Flutter has no Meta Quest, Oculus, or any VR headset SDK. The Meta Quest runs Android under the hood, but Meta's VR SDK is specific to their platform and not integrated with Flutter's rendering pipeline. For headset VR, you need Unity or Unreal Engine.

Does Flutter support Apple Vision Pro?

As of early 2026, Flutter does not have official visionOS support. Apple Vision Pro runs on visionOS, which requires native SwiftUI or RealityKit development. Flutter cannot deploy to visionOS. This is on the Flutter team's long-term roadmap but has no confirmed timeline.

What is the difference between AR and VR in FlutterFlow?

AR (augmented reality) overlays digital content on a real-world camera feed. Flutter supports AR through the ar_flutter_plugin package, which works on iOS ARKit and Android ARCore. VR (virtual reality) is a fully synthetic environment — 360° panoramas in Flutter, not headset VR. Both require Custom Widgets and the Pro plan.

Can I use WebVR (A-Frame) inside FlutterFlow?

Yes, indirectly. You can use a WebView widget to load an A-Frame or Three.js WebVR page inside your FlutterFlow app. This is a workaround that embeds a web VR experience inside a native Flutter shell. It works but adds load time and limits interaction between the WebVR content and your Flutter app's state.

What 360° camera app should I use to take photos for my virtual tour?

For iPhone, use the built-in panorama mode or the Google Street View app (exports equirectangular). For Android, Google Street View and Panorama 360° apps work well. For professional virtual tours, a dedicated camera like Insta360 or Ricoh Theta produces much higher quality equirectangular images.

Will my 360° panorama work on web browsers or only on phones?

panorama_viewer works on mobile (iOS/Android) with full gyroscope support. On web, it works with mouse/touch drag but without gyroscope. The gyroscope-based navigation (tilting your phone to look around) requires a physical device. Cardboard split-screen mode only makes sense on a physical device held in a headset.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.