Using Packages Like sensors and accelerometer to Access Sensor Data in Flutter

Flutter’s rich ecosystem provides powerful packages to access a wide variety of device features. Among these, accessing sensor data like accelerometer, gyroscope, and magnetometer is crucial for building innovative and responsive applications. Utilizing packages such as sensors and accelerometer can greatly simplify this process. This guide delves into how to use these packages to access sensor data in your Flutter applications.

Understanding Sensor Data in Flutter

Sensors in mobile devices provide valuable data about the environment and the device’s movement. Flutter apps can leverage this data for various applications, including:

  • Motion detection and activity tracking
  • Augmented Reality (AR) applications
  • Gaming and immersive experiences
  • Device orientation and gesture recognition

Overview of Packages: sensors and accelerometer

  • sensors package: A comprehensive package that provides access to various device sensors, including accelerometer, gyroscope, and magnetometer. It offers streams of sensor events, making it easy to listen to real-time data.
  • accelerometer package: Specifically focuses on providing accelerometer data. This package is useful when you only need accelerometer readings and want a lightweight solution.

How to Implement Sensor Data Access in Flutter

Let’s walk through the steps to implement sensor data access using both sensors and accelerometer packages.

Method 1: Using the sensors Package

Step 1: Add the sensors Package

Add the sensors dependency to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  sensors: ^3.0.2  # Use the latest version

Then, run flutter pub get to install the package.

Step 2: Import the Package

Import the sensors package in your Dart file:

import 'package:sensors/sensors.dart';
import 'dart:async';
import 'package:flutter/material.dart';
Step 3: Accessing Accelerometer Data

Here’s how to access and display accelerometer data:


import 'package:flutter/material.dart';
import 'package:sensors/sensors.dart';
import 'dart:async';

class SensorDataScreen extends StatefulWidget {
  @override
  _SensorDataScreenState createState() => _SensorDataScreenState();
}

class _SensorDataScreenState extends State {
  List? _accelerometerValues;
  final _streamSubscriptions = >[];

  @override
  Widget build(BuildContext context) {
    final accelerometerValues =
        _accelerometerValues?.map((double v) => v.toStringAsFixed(1)).toList();

    return Scaffold(
      appBar: AppBar(
        title: Text("Sensor Data"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              "Accelerometer",
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            Padding(
              padding: EdgeInsets.all(16.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text('X: ${accelerometerValues?[0] ?? '0.0'}'),
                  Text('Y: ${accelerometerValues?[1] ?? '0.0'}'),
                  Text('Z: ${accelerometerValues?[2] ?? '0.0'}'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    _streamSubscriptions.add(
      accelerometerEvents.listen(
        (AccelerometerEvent event) {
          setState(() {
            _accelerometerValues = [event.x, event.y, event.z];
          });
        },
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    for (final subscription in _streamSubscriptions) {
      subscription.cancel();
    }
  }
}

Explanation:

  • Import Packages: Imports the necessary packages, including sensors, dart:async, and flutter/material.dart.
  • State Variables:
    • _accelerometerValues: A list to store the accelerometer values (x, y, z).
    • _streamSubscriptions: A list to hold the stream subscriptions for proper disposal.
  • Build Method:
    • Displays the accelerometer data (x, y, z) in a simple UI using Text widgets.
    • Uses toStringAsFixed(1) to format the accelerometer values to one decimal place.
    • Handles null safety with the ?? operator to display ‘0.0’ if the accelerometer values are null.
  • initState Method:
    • Subscribes to accelerometerEvents to listen for accelerometer data.
    • Updates the _accelerometerValues state when new data arrives.
  • dispose Method:
    • Cancels all stream subscriptions to prevent memory leaks when the widget is disposed of.

Method 2: Using the accelerometer Package

Step 1: Add the accelerometer Package

Add the accelerometer dependency to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  accelerometer: ^1.0.1  # Use the latest version

Then, run flutter pub get to install the package.

Step 2: Import the Package

Import the accelerometer package in your Dart file:

import 'package:accelerometer/accelerometer.dart';
import 'dart:async';
import 'package:flutter/material.dart';
Step 3: Accessing Accelerometer Data

Here’s how to access and display accelerometer data using the accelerometer package:


import 'package:flutter/material.dart';
import 'package:accelerometer/accelerometer.dart';
import 'dart:async';

class AccelerometerDataScreen extends StatefulWidget {
  @override
  _AccelerometerDataScreenState createState() => _AccelerometerDataScreenState();
}

class _AccelerometerDataScreenState extends State {
  List? _accelerometerValues;
  final _streamSubscriptions = >[];

  @override
  Widget build(BuildContext context) {
    final accelerometerValues =
        _accelerometerValues?.map((double v) => v.toStringAsFixed(1)).toList();

    return Scaffold(
      appBar: AppBar(
        title: Text("Accelerometer Data"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              "Accelerometer",
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            Padding(
              padding: EdgeInsets.all(16.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text('X: ${accelerometerValues?[0] ?? '0.0'}'),
                  Text('Y: ${accelerometerValues?[1] ?? '0.0'}'),
                  Text('Z: ${accelerometerValues?[2] ?? '0.0'}'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    _streamSubscriptions.add(
      accelerometerEvents.listen(
        (AccelerometerEvent event) {
          setState(() {
            _accelerometerValues = [event.x, event.y, event.z];
          });
        },
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    for (final subscription in _streamSubscriptions) {
      subscription.cancel();
    }
  }
}

The code structure and functionality are largely similar to the sensors package example, but it specifically uses accelerometerEvents from the accelerometer package.

Complete Example: Integrating Sensor Data into a Flutter App

Here’s a full example that integrates sensor data into a simple Flutter application with two tabs, one for sensors package and another for the accelerometer package.


import 'package:flutter/material.dart';
import 'package:sensors/sensors.dart';
import 'package:accelerometer/accelerometer.dart' as acc;
import 'dart:async';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            title: Text('Sensor Data Demo'),
            bottom: TabBar(
              tabs: [
                Tab(text: 'Sensors Package'),
                Tab(text: 'Accelerometer Package'),
              ],
            ),
          ),
          body: TabBarView(
            children: [
              SensorDataScreen(),
              AccelerometerDataScreen(),
            ],
          ),
        ),
      ),
    );
  }
}

class SensorDataScreen extends StatefulWidget {
  @override
  _SensorDataScreenState createState() => _SensorDataScreenState();
}

class _SensorDataScreenState extends State {
  List? _accelerometerValues;
  final _streamSubscriptions = >[];

  @override
  Widget build(BuildContext context) {
    final accelerometerValues =
        _accelerometerValues?.map((double v) => v.toStringAsFixed(1)).toList();

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              "Sensors Package",
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            Padding(
              padding: EdgeInsets.all(16.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text('X: ${accelerometerValues?[0] ?? '0.0'}'),
                  Text('Y: ${accelerometerValues?[1] ?? '0.0'}'),
                  Text('Z: ${accelerometerValues?[2] ?? '0.0'}'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    _streamSubscriptions.add(
      accelerometerEvents.listen(
        (AccelerometerEvent event) {
          setState(() {
            _accelerometerValues = [event.x, event.y, event.z];
          });
        },
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    for (final subscription in _streamSubscriptions) {
      subscription.cancel();
    }
  }
}

class AccelerometerDataScreen extends StatefulWidget {
  @override
  _AccelerometerDataScreenState createState() => _AccelerometerDataScreenState();
}

class _AccelerometerDataScreenState extends State {
  List? _accelerometerValues;
  final _streamSubscriptions = >[];

  @override
  Widget build(BuildContext context) {
    final accelerometerValues =
        _accelerometerValues?.map((double v) => v.toStringAsFixed(1)).toList();

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              "Accelerometer Package",
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            Padding(
              padding: EdgeInsets.all(16.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text('X: ${accelerometerValues?[0] ?? '0.0'}'),
                  Text('Y: ${accelerometerValues?[1] ?? '0.0'}'),
                  Text('Z: ${accelerometerValues?[2] ?? '0.0'}'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    _streamSubscriptions.add(
      acc.accelerometerEvents.listen(
        (acc.AccelerometerEvent event) {
          setState(() {
            _accelerometerValues = [event.x, event.y, event.z];
          });
        },
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    for (final subscription in _streamSubscriptions) {
      subscription.cancel();
    }
  }
}

Best Practices for Sensor Data Access

  • Handle Permissions:
    Ensure that you handle runtime permissions properly. Android 6.0 (API level 23) and higher require runtime permissions for accessing sensor data.
  • Optimize Sensor Usage:
    Avoid excessive sensor usage to conserve battery life. Only listen to sensor data when needed and unregister listeners when they are no longer required.
  • Calibrate Sensors:
    Some sensors may require calibration to provide accurate readings. Implement calibration routines if necessary.
  • Error Handling:
    Implement error handling to gracefully handle scenarios where sensor data is unavailable or inaccurate.
  • Stream Management: Always remember to cancel the stream subscriptions in the dispose method to prevent memory leaks.

Conclusion

Accessing sensor data in Flutter using packages like sensors and accelerometer allows developers to create powerful and engaging applications. By following this guide, you can easily integrate sensor data access into your Flutter apps, enabling a wide range of functionalities from motion detection to augmented reality experiences. Remember to handle permissions, optimize sensor usage, and manage streams effectively for a robust and battery-friendly application.