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
: ARoomData
object with information about the scanned room.metadata
: AScanMetadata
object with details about the session.confidence
: AScanConfidence
object indicating the scan's quality.
-
RoomData
: Contains the physical properties of the room.dimensions
: ARoomDimensions
object with metric and imperial support.walls
: A list ofWallData
objects.objects
: A list ofObjectData
objects (e.g., table, chair).doors
: A list ofOpeningData
for doors.windows
: A list ofOpeningData
for windows.openings
: A list ofOpeningData
for generic openings.floor
: AWallData
object representing the floor.ceiling
: AWallData
object 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
: APosition
object (Vector3
) representing the center point.dimensions
: DetailedRoomDimensions
with dual unit support.transform
: AMatrix4
object 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
: ADuration
object.scanDate
: TheDateTime
when 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
: Adouble
from 0.0 to 1.0.wallAccuracy
: Adouble
for the accuracy of wall detection.dimensionAccuracy
: Adouble
for 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
NSCameraUsageDescription
to 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
|.accurate
timeoutSeconds
: 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