flutter_device_compass 2.2.0
flutter_device_compass: ^2.2.0 copied to clipboard
A Flutter compass plugin using device sensors. Heading is 0-360 degrees (0 = north).
flutter_device_compass #
A Flutter compass plugin using device sensors. Provides real-time device heading, orientation-compensated azimuth (0°–360°, where 0° is North), camera-mode heading for AR/camera overlays, and sensor accuracy.
Community-maintained fork of medyas/flutter_compass_v2 (lineage from hemanthrajv/flutter_compass), republished as flutter_device_compass on pub.dev for ongoing maintenance and modern Flutter (3.24+ / 3.44+) & Gradle/Kotlin compatibility.
Features #
- 🧭 Heading Calculation: 0° to 360°, where 0° is North.
- 📱 Orientation-Aware: Automatically compensates for device orientation changes (Portrait, Landscape Left, Landscape Right, Portrait Upside Down).
- 📷 Camera Mode Heading: Heading out the back of the device (
headingForCameraMode), ideal for Augmented Reality (AR) or camera overlays. On Android this usesSensorManager.remapCoordinateSystem(..., AXIS_X, AXIS_Z, ...)per platform docs. - 🎯 Sensor Accuracy: Exposes calibration and accuracy deviation in degrees (
accuracy). - 🔍 Hardware Detection: Proactively check sensor availability via
FlutterCompass.hasSensorsbefore mounting compass views. - ⚡ Robust Sensor Fallbacks: Uses
Sensor.TYPE_ROTATION_VECTORon Android when available with low-pass filtered accelerometer + magnetometer fallback. - 🔋 Tunable updates:
CompassUpdateOptionspresets (fine0.1° default,balanced0.5°,batterySaver1.0°) control heading filter, motion rate, and emit interval. - 🛡️ Crash-Resistant: Guarded against non-finite /
NaNsensor readings and negative true headings.
Getting Started #
Add flutter_device_compass to your pubspec.yaml:
dependencies:
flutter_device_compass: ^2.2.0
Platform Setup #
iOS #
iOS requires location permissions to deliver compass/heading updates via CoreLocation.
Add the following keys to your ios/Runner/Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to heading and compass data to orient the map.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs access to heading and compass data to orient the map.</string>
Note: Use a permission package like
permission_handlerto request location permissions at runtime before listening to compass events on iOS.
Android #
No additional permissions are strictly required for sensor access. However, if your app utilizes fine location for geomagnetic declination or maps alongside the compass, add the following to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Usage Examples #
Backward compatibility (no breaking change) #
Existing apps keep working with zero code changes:
// Legacy API — still valid. EventChannel listen args are null;
// native plugins apply built-in balanced defaults.
FlutterCompass.events?.listen((event) { /* ... */ });
New update tuning is opt-in via nullable [CompassUpdateOptions?]:
| Call | Platform listen arguments | Behavior |
|---|---|---|
FlutterCompass.events |
null |
Same as ≤2.1.1; native fine (0.1°) defaults |
FlutterCompass.eventsFor() |
null |
Same as events |
FlutterCompass.eventsFor(CompassUpdateOptions.balanced) |
{ headingFilter, motionUpdateHz, updateIntervalMs } |
Custom profile |
FlutterCompass.eventsFor(null) |
null |
Explicit legacy path |
Native code treats each map field as optional: missing/null keys keep platform defaults. Old app binaries talking to a new plugin (or the reverse during upgrades) therefore stay safe as long as they use FlutterCompass.events.
1. Basic Stream Subscription #
import 'package:flutter_device_compass/flutter_device_compass.dart';
// Default — unchanged from older versions:
final subscription = FlutterCompass.events?.listen((CompassEvent event) {
print('Heading: ${event.heading}°');
print('Camera Mode Heading: ${event.headingForCameraMode}°');
print('Accuracy Deviation: ±${event.accuracy}°');
});
// Optional new API (additive):
FlutterCompass.eventsFor(CompassUpdateOptions.balanced)?.listen((event) { /* ... */ });
FlutterCompass.eventsFor(CompassUpdateOptions.batterySaver)?.listen((event) { /* ... */ });
FlutterCompass.eventsFor(const CompassUpdateOptions(
headingFilter: 0.1,
motionUpdateHz: 30,
updateIntervalMs: 33,
))?.listen((event) { /* ... */ });
// Cancel when no longer needed
// subscription?.cancel();
// Before switching profiles: cancel listeners, then FlutterCompass.resetEvents();
Choosing headingFilter (and related knobs)
| Preset | headingFilter |
Typical feel | Battery | Defaults applied |
|---|---|---|---|---|
CompassUpdateOptions.fine (default) |
0.1° | Very smooth / chatty needle; historical default | Higher | motion 30 Hz, emit ≥33 ms |
CompassUpdateOptions.balanced |
0.5° | Smooth enough for most UIs | Medium | motion 20 Hz, emit ≥50 ms |
CompassUpdateOptions.batterySaver |
1.0° | Slightly steppier; fewer callbacks | Lower | motion 15 Hz, emit ≥66 ms |
- iOS:
headingFilter→CLLocationManager.headingFilter;motionUpdateHz→ CoreMotion sample rate (camera heading);updateIntervalMscaps Flutter events. - Android:
headingFilteris a minimum heading delta between emissions;updateIntervalMssets emit throttle and sensor delay hint;motionUpdateHzis ignored. - When switching options at runtime, cancel the previous subscription (or call
FlutterCompass.resetEvents()after cancel) so the EventChannel can restart with new args.
2. Building a Rotating Compass UI (StreamBuilder) #
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_device_compass/flutter_device_compass.dart';
class CompassWidget extends StatelessWidget {
const CompassWidget({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<CompassEvent>(
stream: FlutterCompass.events,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
final heading = snapshot.data?.heading;
// On devices without compass sensors, heading will be null
if (heading == null) {
return const Center(
child: Text('Device does not have compass sensors'),
);
}
return Center(
child: Transform.rotate(
angle: (heading * (math.pi / 180) * -1),
child: Image.asset(
'assets/compass.png',
width: 250,
height: 250,
),
),
);
},
);
}
}
3. Check Sensor Availability #
Before initiating compass-dependent features, you can verify if the device hardware supports compass sensors:
final bool? isSupported = await FlutterCompass.hasSensors;
if (isSupported == true) {
print('Compass sensor is available on this device');
} else {
print('Device does not support compass/heading sensors');
}
4. Read Current Heading Once #
If you only need a single heading snapshot (e.g. when capturing a photo or logging location):
Future<void> logCurrentHeading() async {
final CompassEvent event = await FlutterCompass.events!.first;
print('Current heading is: ${event.heading}°');
}
CompassEvent Reference #
| Property | Type | Description |
|---|---|---|
heading |
double? |
The device heading in degrees around its Z-axis (pointing direction of the top of the phone). Range: 0.0 to 360.0, where 0.0 is North. null if sensor is unavailable. |
headingForCameraMode |
double? |
The heading in degrees out the back of the phone (rear camera / AR). Ideal for camera overlays. |
accuracy |
double? |
Estimated deviation error in degrees (±). null if uncalibrated or unavailable. |
Releases #
GitHub Releases / tags track pub.dev versions. Check Releases for changelogs per version.
Maintainer #
Maintained with ❤️ by Nafis Kabbo.
Contributions, issues, and feature requests are welcome on GitHub!