Flutter RoomPlan
A Flutter plugin that allows you to use Apple's RoomPlan API to scan an interior room and get a 3D model and measurements.
Requirements
- iOS 16.0+
- A device with a LiDAR sensor is required (e.g., iPhone 12 Pro or newer Pro models, iPhone 16 Pro/Pro Max, iPad Pro).
Installation
First, add roomplan_flutter to your pubspec.yaml dependencies:
dependencies:
roomplan_flutter: ^0.1.1 # Replace with the latest version
Then, add the required NSCameraUsageDescription to your ios/Runner/Info.plist file to explain why your app needs camera access:
<key>NSCameraUsageDescription</key>
<string>This app uses the camera to scan your room and create a 3D model.</string>
Finally, run flutter pub get.
Usage
Here's a basic example of how to use the RoomPlanScanner in a Flutter widget.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:roomplan_flutter/roomplan_flutter.dart';
class ScannerWidget extends StatefulWidget {
const ScannerWidget({super.key});
@override
State<ScannerWidget> createState() => _ScannerWidgetState();
}
class _ScannerWidgetState extends State<ScannerWidget> {
late final RoomPlanScanner _roomScanner;
StreamSubscription<ScanResult?>? _scanSubscription;
bool _isSupported = false;
bool _isScanning = false;
MeasurementUnit _selectedUnit = MeasurementUnit.metric;
@override
void initState() {
super.initState();
_checkSupport();
}
Future<void> _checkSupport() async {
final supported = await RoomPlanScanner.isSupported();
setState(() {
_isSupported = supported;
});
if (supported) {
_roomScanner = RoomPlanScanner();
// Listen to real-time updates
_scanSubscription = _roomScanner.onScanResult.listen((result) {
if (result != null) {
print('Room updated! Walls: ${result.room.walls.length}');
if (result.room.dimensions != null) {
// Display dimensions in selected unit system
final dims = result.room.dimensions!;
print('Room size: ${dims.getFormattedLength(_selectedUnit)} x ${dims.getFormattedWidth(_selectedUnit)}');
}
}
});
}
}
@override
void dispose() {
_scanSubscription?.cancel();
if (_isSupported) {
_roomScanner.dispose();
}
super.dispose();
}
Future<void> _startScan() async {
if (!_isSupported) return;
setState(() => _isScanning = true);
try {
final result = await _roomScanner.startScanning();
if (result != null) {
print('Scan complete! Room has ${result.room.walls.length} walls.');
// Process your scan result here
}
} on RoomPlanPermissionsException {
print('Camera permission denied. Please grant camera access.');
} on ScanCancelledException {
print('Scan was cancelled by the user.');
} catch (e) {
print('Error during scan: $e');
} finally {
setState(() => _isScanning = false);
}
}
Future<void> _stopScan() async {
if (_isSupported && _isScanning) {
await _roomScanner.stopScanning();
setState(() => _isScanning = false);
}
}
@override
Widget build(BuildContext context) {
if (!_isSupported) {
return const Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'RoomPlan is not supported on this device.\n'
'Requires iOS 16+ and LiDAR sensor.',
textAlign: TextAlign.center,
),
),
);
}
return Column(
children: [
if (_isScanning) ...[
const CircularProgressIndicator(),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _stopScan,
child: const Text('Stop Scan'),
),
] else
ElevatedButton(
onPressed: _startScan,
child: const Text('Start Room Scan'),
),
],
);
}
}
Checking Device Compatibility
Before starting a scan, you should check if the device supports RoomPlan:
final isSupported = await RoomPlanScanner.isSupported();
if (!isSupported) {
// Show appropriate message to user
print('RoomPlan requires iOS 16+ and a LiDAR-enabled device');
}
Scan Configuration
You can customize the scanning behavior using ScanConfiguration:
final config = ScanConfiguration(
quality: ScanQuality.accurate, // fast, balanced, accurate
timeoutSeconds: 300, // 5 minute timeout
enableRealtimeUpdates: true, // Real-time scan updates
detectFurniture: true, // Include furniture detection
detectDoors: true, // Include door detection
detectWindows: true, // Include window detection
);
final result = await _roomScanner.startScanning(configuration: config);
Unit System Support
The package supports both metric and imperial measurement units:
// Switch between measurement units
MeasurementUnit selectedUnit = MeasurementUnit.metric; // or MeasurementUnit.imperial
// Get formatted dimensions in selected unit
if (result.room.dimensions != null) {
final dims = result.room.dimensions!;
print('Length: ${dims.getFormattedLength(selectedUnit)}'); // "5.00m" or "16.40ft"
print('Width: ${dims.getFormattedWidth(selectedUnit)}'); // "4.00m" or "13.12ft"
print('Area: ${dims.getFormattedFloorArea(selectedUnit)}'); // "20.00m²" or "215.28sq ft"
print('Volume: ${dims.getFormattedVolume(selectedUnit)}'); // "60.00m³" or "2118.88cu ft"
}
### Floor and Ceiling Measurements
The plugin derives complete floor and ceiling surfaces from the scan:
```dart
final result = await _roomScanner.startScanning();
if (result?.room.floor != null) {
final floor = result!.room.floor!;
print('Floor area: ${floor.dimensions?.getFormattedFloorArea(MeasurementUnit.metric)}');
print('Floor size: ${floor.dimensions?.getFormattedLength(MeasurementUnit.metric)} x '
'${floor.dimensions?.getFormattedWidth(MeasurementUnit.metric)}');
print('Floor confidence: ${floor.confidence}');
}
if (result?.room.ceiling != null) {
final ceiling = result!.room.ceiling!;
print('Ceiling area: ${ceiling.dimensions?.getFormattedFloorArea(MeasurementUnit.metric)}');
print('Ceiling size: ${ceiling.dimensions?.getFormattedLength(MeasurementUnit.metric)} x '
'${ceiling.dimensions?.getFormattedWidth(MeasurementUnit.metric)}');
print('Ceiling confidence: ${ceiling.confidence}');
}
Notes:
- Floor and ceiling dimensions are length × width; room height is available via
room.dimensions.height. - Confidence is a conservative aggregation from detected walls.
// Access raw imperial values directly final lengthInFeet = result.room.dimensions!.lengthInFeet; final areaInSqFeet = result.room.dimensions!.floorAreaInSqFeet;
### Performance Monitoring
The package includes built-in performance monitoring (in debug mode):
```dart
import 'package:roomplan_flutter/src/performance/performance_monitor.dart';
// Enable memory monitoring
PerformanceMonitor.startMemoryMonitoring();
// Get performance statistics
final stats = PerformanceMonitor.getPerformanceStats();
print('Average JSON parse time: ${stats['operation_averages']['json_parse_total']}μs');
// Clear performance data
PerformanceMonitor.clearStats();
Error Handling
The plugin provides specific exceptions for different error scenarios:
RoomPlanPermissionsException: Camera permission was deniedScanCancelledException: User cancelled the scanLowPowerModeException: Device is in low power modeInsufficientStorageException: Not enough storage spaceWorldTrackingFailedException: ARKit world tracking failedMemoryPressureException: Device experiencing memory pressure- Plus 15+ additional specific error types for better debugging
See the example app for a more detailed implementation.
Performance Features
Memory Optimization
- Object Pooling: Reuses Matrix4 and Vector3 objects to reduce garbage collection
- Stream Caching: Cached broadcast streams prevent repeated creation
- Automatic Cleanup: Timer-based maintenance frees unused resources
- Memory Monitoring: Automatic detection and handling of memory pressure
Processing Optimization
- 3x Faster JSON Parsing: Optimized algorithms and caching
- Single-Pass Calculations: Reduced complexity from O(n²) to O(n)
- Lazy Evaluation: Only compute values when needed
- Pre-computed Lookups: Enum conversions use lookup tables
UI Responsiveness
- Throttled Updates: Statistics updated on 500ms timer instead of every frame
- Reduced Rebuilds: Minimal setState() calls during real-time scanning
- Consistent 60fps: Maintained throughout scanning sessions
Data Models
The plugin returns a ScanResult object, which contains a tree of structured data.
-
ScanResult: The root object containing the full scan details.room: ARoomDataobject with information about the scanned room.metadata: AScanMetadataobject with details about the session.confidence: AScanConfidenceobject indicating the scan's quality.
-
RoomData: Contains the physical properties of the room.dimensions: ARoomDimensionsobject with metric and imperial support.walls: A list ofWallDataobjects.objects: A list ofObjectDataobjects (e.g., table, chair).doors: A list ofOpeningDatafor doors.windows: A list ofOpeningDatafor windows.openings: A list ofOpeningDatafor generic openings.floor: AWallDataobject representing the floor.ceiling: AWallDataobject representing the ceiling.
-
RoomDimensions: Enhanced with dual unit support.length,width,height: Base measurements in meters.lengthInFeet,widthInFeet,heightInFeet: Imperial equivalents.floorArea,volume,perimeter: Calculated values.floorAreaInSqFeet,volumeInCuFeet: Imperial calculated values.getFormattedLength(),getFormattedArea(), etc.: Formatted display methods.- Complete JSON serialization support.
-
WallData,ObjectData,OpeningData: These models describe a physical entity and share common fields:uuid: A unique identifier for the entity.position: APositionobject (Vector3) representing the center point.dimensions: DetailedRoomDimensionswith dual unit support.transform: AMatrix4object for the 3D transform (position, rotation).confidence: An enum (Confidence.low,medium,high) for the detected entity.- Complete JSON serialization/deserialization support.
-
ScanMetadata: Contains metadata about the scanning session.scanDuration: ADurationobject.scanDate: TheDateTimewhen the scan started.deviceModel: The model of the device (e.g., "iPhone14,3").hasLidar: A boolean indicating if the device has a LiDAR sensor.
-
ScanConfidence: Contains confidence values for different aspects of the scan.overall: Adoublefrom 0.0 to 1.0.wallAccuracy: Adoublefor the accuracy of wall detection.dimensionAccuracy: Adoublefor the accuracy of measurements.
Refer to the source code for detailed information on all fields.
Troubleshooting
Common Issues
"RoomPlan is not supported"
- Ensure your device has iOS 16.0 or later
- Verify your device has a LiDAR sensor (iPhone 12 Pro+, iPad Pro with LiDAR)
- Check that your app's deployment target is set to iOS 16.0+
Camera permission denied
- Add
NSCameraUsageDescriptionto yourInfo.plist - The user must grant camera permission when prompted
- Users can change permissions in Settings > Privacy & Security > Camera
Scanning accuracy issues
- Ensure good lighting conditions
- Move slowly and steadily during scanning
- Keep the camera pointed at walls and objects
- Avoid reflective surfaces and windows when possible
Memory issues or crashes
- Always call
dispose()on the scanner when done - Cancel stream subscriptions in your widget's
dispose()method - Avoid creating multiple scanner instances simultaneously
Best Practices
- Check compatibility first: Always call
RoomPlanScanner.isSupported()before creating a scanner - Handle permissions gracefully: Provide clear messaging when camera access is denied
- Provide user guidance: Show instructions on how to scan effectively
- Memory management: Always dispose resources properly
- Error handling: Implement comprehensive error handling for all scan scenarios
API Reference
RoomPlanScanner
Static Methods
static Future<bool> isSupported()- Check if RoomPlan is available on the current device
Instance Methods
Future<ScanResult?> startScanning({ScanConfiguration? configuration})- Begin a room scanning session with optional configurationFuture<void> stopScanning()- Stop the current scanning sessionvoid dispose()- Clean up resources
Properties
Stream<ScanResult?> onScanResult- Stream of real-time scan updates
ScanConfiguration
quality:ScanQuality.fast|.balanced|.accuratetimeoutSeconds: Optional timeout in secondsenableRealtimeUpdates: Enable real-time scan updatesdetectFurniture: Include furniture in scan resultsdetectDoors: Include doors in scan resultsdetectWindows: Include windows in scan results
Preset Configurations
ScanConfiguration.fast(): Quick scanning with basic featuresScanConfiguration.accurate(): High-quality scanning with all featuresScanConfiguration.minimal(): Minimal scanning for testing
MeasurementUnit
MeasurementUnit.metric: Meters, square meters, cubic metersMeasurementUnit.imperial: Feet, square feet, cubic feet
Unit Conversion
UnitConverter.metersToFeetConversion(): Convert meters to feetUnitConverter.sqMetersToSqFeetConversion(): Convert square meters to square feetUnitConverter.formatLength(): Format length with unitsUnitConverter.formatArea(): Format area with units
Exceptions
Permission & Access
RoomPlanPermissionsException- Camera permission deniedCameraPermissionNotDeterminedException- Permission not yet requestedCameraPermissionUnknownException- Unknown permission state
Device & Platform
RoomPlanNotAvailableException- RoomPlan not supportedUnsupportedVersionException- iOS version too oldARKitNotSupportedException- ARKit not availableInsufficientHardwareException- Device lacks required hardware
Scanning Process
ScanCancelledException- User cancelled the scanSessionInProgressException- Scan already in progressSessionNotRunningException- No active scan sessionWorldTrackingFailedException- ARKit tracking failedScanFailedException- General scan failureProcessingFailedException- Data processing failed
System Resources
LowPowerModeException- Device in low power modeInsufficientStorageException- Not enough storage spaceMemoryPressureException- System memory pressureDeviceOverheatingException- Device overheatingBackgroundModeActiveException- App backgrounded during scan
Data & Network
TimeoutException- Operation timed outDataCorruptedException- Scan data corruptedExportFailedException- Failed to export scan dataNetworkRequiredException- Network connection requiredUIErrorException- User interface error
Testing Guide for Flutter RoomPlan
Device Requirements for Testing
Supported Devices
To properly test the Flutter RoomPlan package, you need a device with:
- iOS 16.0 or later
- LiDAR sensor
Compatible Devices:
- iPhone 12 Pro / Pro Max
- iPhone 13 Pro / Pro Max
- iPhone 14 Pro / Pro Max
- iPhone 15 Pro / Pro Max
- iPhone 16 Pro / Pro Max
- iPad Pro 11-inch (4th generation and later)
- iPad Pro 12.9-inch (5th generation and later)
Non-Compatible Devices
These devices will return isSupported() = false:
- iPhone 12 / 12 Mini
- iPhone 13 / 13 Mini
- iPhone 14 / 14 Plus
- iPhone 15 / 15 Plus
- iPhone 16 / 16 Plus
- Any device running iOS < 16.0
Testing Checklist
1. Unit Tests
Run automated tests on any development machine:
flutter test
Expected results:
- ✅ All RoomPlanScanner method tests pass
- ✅ All model validation tests pass
- ✅ All exception handling tests pass
- ✅ All JSON parsing tests pass
2. Device Compatibility Testing
On Compatible Device:
final isSupported = await RoomPlanScanner.isSupported();
// Should return: true
On Incompatible Device:
final isSupported = await RoomPlanScanner.isSupported();
// Should return: false
3. Permission Testing
Test Camera Permission Flow:
- First run - should prompt for camera permission
- Grant permission - scanning should work
- Deny permission - should throw
RoomPlanPermissionsException - Test permission changes in iOS Settings
4. Scanning Functionality Tests
Basic Scanning Test:
final scanner = RoomPlanScanner();
try {
final result = await scanner.startScanning();
if (result != null) {
print('✅ Scan completed successfully');
print('Room dimensions: ${result.room.dimensions}');
print('Walls found: ${result.room.walls.length}');
print('Objects found: ${result.room.objects.length}');
}
} catch (e) {
print('❌ Scan failed: $e');
}
Real-time Updates Test:
final scanner = RoomPlanScanner();
scanner.onScanResult.listen((result) {
if (result != null) {
print('📊 Real-time update: ${result.room.walls.length} walls');
}
});
Stop Scanning Test:
final scanner = RoomPlanScanner();
// Start scanning in background
scanner.startScanning();
// Stop after 10 seconds
await Future.delayed(Duration(seconds: 10));
await scanner.stopScanning();
5. Error Scenario Testing
Test Different Error Conditions:
- Camera permission denied
- User cancels scan
- Device orientation changes during scan
- App backgrounding during scan
- Multiple scanner instances
- Memory pressure scenarios
6. Performance Testing
Memory Usage:
- Monitor memory usage during long scans
- Test multiple scan sessions
- Verify proper resource cleanup with
dispose()
Battery Usage:
- Monitor battery drain during scanning
- Test with screen brightness settings
- Compare performance in different room sizes
7. Room Scenarios Testing
Test Different Room Types:
- Small rooms (< 3x3 meters)
- Large rooms (> 6x6 meters)
- Complex layouts (L-shaped, multiple doorways)
- Furnished vs empty rooms
- Different lighting conditions
Environmental Challenges:
- Reflective surfaces (mirrors, glass)
- Dark rooms (poor lighting)
- Cluttered spaces
- Outdoor spaces (should fail gracefully)
Test Results Documentation
Create Test Report:
Document your testing with:
## Test Report - Flutter RoomPlan v0.0.8
**Device**: iPhone 14 Pro (iOS 17.1)
**Date**: [Current Date]
### Compatibility Tests
- ✅ isSupported() returns true
- ✅ Camera permission prompt appears
- ✅ Scan initializes successfully
### Functionality Tests
- ✅ Basic room scan completes
- ✅ Real-time updates received
- ✅ Stop scanning works correctly
- ✅ Room dimensions detected: 4.2m x 3.8m x 2.7m
- ✅ Objects detected: 3 (table, chair, bookshelf)
- ✅ Walls detected: 4
- ✅ Doors detected: 1
- ✅ Windows detected: 2
### Error Handling Tests
- ✅ Permission denied handled correctly
- ✅ User cancellation handled correctly
- ✅ Background/foreground transitions work
### Performance Tests
- Memory usage: ~150MB during scan
- Battery usage: ~8%/hour during active scanning
- Scan completion time: 45 seconds average
### Issues Found
- None / [List any issues discovered]
### Recommendations
- [Any recommendations for improvements]
Known Limitations
- Simulator Testing: Cannot test actual scanning functionality in simulator
- macOS Testing: RoomPlan is iOS-only, no macOS support
- Lighting Requirements: Requires adequate lighting for best results
- Room Size Limits: Very large rooms (>10m) may have accuracy issues
- Surface Requirements: Plain walls work better than textured/patterned walls
Reporting Issues
When reporting issues, please include:
- Device model and iOS version
- Flutter/Dart version
- Room size and characteristics
- Lighting conditions
- Complete error messages and stack traces
- Steps to reproduce
- Expected vs actual behavior
Submit issues at: https://github.com/Barba2k2/flutter_roomplan/issues
Libraries
- api/exceptions
- api/models/confidence
- api/models/measurement_unit
- api/models/object_data
- api/models/opening_data
- api/models/position
- api/models/room_data
- api/models/room_dimensions
- api/models/scan_confidence
- api/models/scan_configuration
- api/models/scan_metadata
- api/models/scan_result
- api/models/wall_data
- api/room_plan_scanner
- roomplan_flutter