motion_core 0.0.2
motion_core: ^0.0.2 copied to clipboard
A high-performance Flutter plugin that provides a simple, unified stream of fused device motion data, abstracting native APIs like iOS Core Motion and Android's Rotation Vector sensor.
motion_core #
A Flutter plugin that provides a simple, high-performance, and unified stream of fused device motion data from native platform APIs.
Overview #
While Flutter provides access to raw sensors (via plugins like sensors_plus), there is no out-of-the-box equivalent to iOS Core Motion or Android's Fused Sensor APIs. To get a device's true orientation (attitude), developers typically need to implement complex sensor fusion algorithms (like Madgwick or Mahony filters) in Dart or build a native bridge.
motion_core is that bridge. It does the native work for you, exposing a single, clean stream of calibrated motion data by leveraging the best available technology on each platform.
Features #
- ✅ Unified Stream: Get a single
Stream<MotionData>that works seamlessly on both iOS and Android. - 🚀 Native Performance: Leverages iOS
CoreMotion(CMDeviceMotion) and Android'sTYPE_ROTATION_VECTORfor battery-efficient, low-latency, hardware-accelerated fusion. - 📦 Comprehensive Motion Data: Provides the complete motion state in one object:
- Attitude: Device orientation as a
Quaternion. - Gravity: A
Vector3representing the direction of gravity. - User Acceleration: A
Vector3of acceleration applied by the user, with gravity removed. - Heading Accuracy: The estimated accuracy of the compass heading (in radians) on Android.
- Attitude: Device orientation as a
- 🛠️ Sensor Availability Check: A simple async utility (
MotionCore.isAvailable()) to check if the device has the necessary hardware.
Getting Started #
1. Add to pubspec.yaml #
Add motion_core and vector_math to your project's dependencies.
dependencies:
flutter:
sdk: flutter
motion_core: ^1.0.0 # Use the latest version from pub.dev
vector_math: ^2.1.4
2. Install #
Run the following command in your terminal:
flutter pub get
Platform Specific Setup #
This plugin requires platform-specific configuration to access motion data.
iOS (Mandatory) #
You must add a usage description to your ios/Runner/Info.plist file. Without this, your app will crash on recent iOS versions when trying to access motion data and will be rejected by the App Store.
Open ios/Runner/Info.plist and add the following:
<key>NSMotionUsageDescription</key>
<string>This app requires access to motion data to determine device orientation and movement.</string>
Warning Failure to add the
NSMotionUsageDescriptionkey will result in a runtime crash. This is not optional.
Android #
No permissions are required.
Access to the standard motion sensors (accelerometer, gyroscope, etc.) on Android does not require any special permissions in your AndroidManifest.xml.
Usage #
Here is a complete example demonstrating how to use the plugin.
Basic Example #
import 'dart:async';
import 'dart:math' show pi;
import 'package:flutter/material.dart';
import 'package:motion_core/motion_core.dart';
import 'package:vector_math/vector_math_64.dart' as v;
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: MotionDemoScreen(),
theme: ThemeData.dark(),
);
}
}
class MotionDemoScreen extends StatefulWidget {
const MotionDemoScreen({super.key});
@override
State<MotionDemoScreen> createState() => _MotionDemoScreenState();
}
class _MotionDemoScreenState extends State<MotionDemoScreen> {
MotionData? _motionData;
StreamSubscription? _motionSubscription;
bool _isAvailable = false;
@override
void initState() {
super.initState();
_checkAvailabilityAndStart();
}
Future<void> _checkAvailabilityAndStart() async {
bool available = await MotionCore.isAvailable();
if (!mounted) return;
setState(() {
_isAvailable = available;
});
if (_isAvailable) {
_startListening();
}
}
void _startListening() {
_motionSubscription = MotionCore.motionStream.listen((MotionData data) {
if (mounted) {
setState(() {
_motionData = data;
});
}
});
}
@override
void dispose() {
_motionSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Motion Core Demo')),
body: Center(
child: !_isAvailable
? const Text('Required motion sensors are not available.')
: _motionData == null
? const CircularProgressIndicator()
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 3D Box visualizer
Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001) // perspective
..multiply(v.Matrix4.fromQuaternion(_motionData!.attitude)),
alignment: FractionalOffset.center,
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.blueAccent.withOpacity(0.8),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white, width: 2),
),
child: const Center(child: Text('FRONT', style: TextStyle(fontWeight: FontWeight.bold))),
),
),
const SizedBox(height: 30),
// Data table
Text('Pitch: ${(_motionData!.pitch * 180 / pi).toStringAsFixed(1)}°'),
Text('Roll: ${(_motionData!.roll * 180 / pi).toStringAsFixed(1)}°'),
Text('Yaw: ${(_motionData!.yaw * 180 / pi).toStringAsFixed(1)}°'),
],
),
),
);
}
}
Managing the Stream #
The motionStream is a broadcast stream. It is important to manage your subscription according to your widget's lifecycle.
// Pause updates when the widget is not visible
_motionSubscription?.pause();
// Resume updates when the widget is visible again
_motionSubscription?.resume();
// Stop listening completely to prevent memory leaks
_motionSubscription?.cancel();
API Details #
The MotionData Object #
The stream provides MotionData objects, which contain the following properties:
| Property | Type | Description |
|---|---|---|
attitude |
Quaternion |
The device's orientation in 3D space. |
gravity |
Vector3 |
The gravity vector, indicating the direction of gravity relative to the device. |
userAcceleration |
Vector3 |
The acceleration applied by the user, with the effect of gravity removed. |
headingAccuracy |
double? |
Android only. The estimated accuracy of the heading in radians. null on iOS. |
pitch |
double |
A convenience getter for the pitch angle (in radians) from the attitude. |
roll |
double |
A convenience getter for the roll angle (in radians) from the attitude. |
yaw |
double |
A convenience getter for the yaw angle (in radians) from the attitude. |
Contributing #
Contributions are welcome! If you find a bug or have a feature request, please open an issue on the GitHub repository.
License #
This project is licensed under the MIT License - see the LICENSE file for details.