Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/flet/lib/src/flet_core_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ import 'services/file_picker.dart';
import 'services/haptic_feedback.dart';
import 'services/semantics_service.dart';
import 'services/shake_detector.dart';
import 'services/sensors.dart';
import 'services/shared_preferences.dart';
import 'services/storage_paths.dart';
import 'services/tester.dart';
Expand Down Expand Up @@ -369,22 +370,32 @@ class FletCoreExtension extends FletExtension {
switch (control.type) {
case "BrowserContextMenu":
return BrowserContextMenuService(control: control);
case "Accelerometer":
return AccelerometerService(control: control);
case "Barometer":
return BarometerService(control: control);
case "Clipboard":
return ClipboardService(control: control);
case "FilePicker":
return FilePickerService(control: control);
case "HapticFeedback":
return HapticFeedbackService(control: control);
case "Gyroscope":
return GyroscopeService(control: control);
case "ShakeDetector":
return ShakeDetectorService(control: control);
case "SharedPreferences":
return SharedPreferencesService(control: control);
case "SemanticsService":
return SemanticsServiceControl(control: control);
case "Magnetometer":
return MagnetometerService(control: control);
case "StoragePaths":
return StoragePaths(control: control);
case "Tester":
return TesterService(control: control);
case "UserAccelerometer":
return UserAccelerometerService(control: control);
case "UrlLauncher":
return UrlLauncherService(control: control);
default:
Expand Down
198 changes: 198 additions & 0 deletions packages/flet/lib/src/services/sensors.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:sensors_plus/sensors_plus.dart';

import '../flet_service.dart';
import '../utils/numbers.dart';
import '../utils/time.dart';

abstract class _SensorStreamService<T> extends FletService {
_SensorStreamService({required super.control});

StreamSubscription<T>? _subscription;
bool _enabled = true;
bool _hasReadingSubscribers = false;
bool _hasErrorSubscribers = false;
Duration _interval = SensorInterval.normalInterval;
bool _cancelOnError = true;

Duration get defaultInterval => SensorInterval.normalInterval;

String get eventName => "reading";

Stream<T> sensorStream(Duration samplingPeriod);

Map<String, dynamic> serializeEvent(T event);

@override
void init() {
super.init();
_updateConfig(forceRestart: true);
}

@override
void update() {
_updateConfig();
}

void _updateConfig({bool forceRestart = false}) {
var enabled = control.getBool("enabled", true) ?? true;
var interval =
control.getDuration("interval", defaultInterval) ?? defaultInterval;
if (interval.isNegative) {
interval = defaultInterval;
}
var hasReadingSubscribers = control.getBool("on_$eventName") == true;
var hasErrorSubscribers = control.getBool("on_error") == true;
var cancelOnError = control.getBool("cancel_on_error", true) ?? true;

if (forceRestart ||
enabled != _enabled ||
interval != _interval ||
hasReadingSubscribers != _hasReadingSubscribers ||
hasErrorSubscribers != _hasErrorSubscribers ||
cancelOnError != _cancelOnError) {
_enabled = enabled;
_interval = interval;
_hasReadingSubscribers = hasReadingSubscribers;
_hasErrorSubscribers = hasErrorSubscribers;
_cancelOnError = cancelOnError;
_restart();
}
}

void _restart() {
_subscription?.cancel();
_subscription = null;

if (!_enabled || (!_hasReadingSubscribers && !_hasErrorSubscribers)) {
return;
}

final samplingPeriod = _interval;
try {
_subscription = sensorStream(samplingPeriod).listen(
(event) {
if (_hasReadingSubscribers) {
final payload = serializeEvent(event);
control.triggerEvent(eventName, payload);
}
},
onError: (error, stackTrace) {
if (_hasErrorSubscribers) {
control.triggerEvent("error",
{"message": error?.toString() ?? "Unknown sensor error"});
} else {
debugPrint(
"Error listening to ${control.type} sensor stream: $error");
}
},
cancelOnError: _cancelOnError,
);
} catch (error) {
debugPrint("Failed to initialize ${control.type} sensor stream: $error");
}
}

@override
void dispose() {
_subscription?.cancel();
_subscription = null;
super.dispose();
}
}

class AccelerometerService extends _SensorStreamService<AccelerometerEvent> {
AccelerometerService({required super.control});

@override
Stream<AccelerometerEvent> sensorStream(Duration samplingPeriod) {
return accelerometerEventStream(samplingPeriod: samplingPeriod);
}

@override
Map<String, dynamic> serializeEvent(AccelerometerEvent event) {
return {
"x": event.x,
"y": event.y,
"z": event.z,
"timestamp": event.timestamp.microsecondsSinceEpoch,
};
}
}

class UserAccelerometerService
extends _SensorStreamService<UserAccelerometerEvent> {
UserAccelerometerService({required super.control});

@override
Stream<UserAccelerometerEvent> sensorStream(Duration samplingPeriod) {
return userAccelerometerEventStream(samplingPeriod: samplingPeriod);
}

@override
Map<String, dynamic> serializeEvent(UserAccelerometerEvent event) {
return {
"x": event.x,
"y": event.y,
"z": event.z,
"timestamp": event.timestamp.microsecondsSinceEpoch,
};
}
}

class GyroscopeService extends _SensorStreamService<GyroscopeEvent> {
GyroscopeService({required super.control});

@override
Stream<GyroscopeEvent> sensorStream(Duration samplingPeriod) {
return gyroscopeEventStream(samplingPeriod: samplingPeriod);
}

@override
Map<String, dynamic> serializeEvent(GyroscopeEvent event) {
return {
"x": event.x,
"y": event.y,
"z": event.z,
"timestamp": event.timestamp.microsecondsSinceEpoch,
};
}
}

class MagnetometerService extends _SensorStreamService<MagnetometerEvent> {
MagnetometerService({required super.control});

@override
Stream<MagnetometerEvent> sensorStream(Duration samplingPeriod) {
return magnetometerEventStream(samplingPeriod: samplingPeriod);
}

@override
Map<String, dynamic> serializeEvent(MagnetometerEvent event) {
return {
"x": event.x,
"y": event.y,
"z": event.z,
"timestamp": event.timestamp.microsecondsSinceEpoch,
};
}
}

class BarometerService extends _SensorStreamService<BarometerEvent> {
BarometerService({required super.control});

@override
Stream<BarometerEvent> sensorStream(Duration samplingPeriod) {
return barometerEventStream(samplingPeriod: samplingPeriod);
}

@override
Map<String, dynamic> serializeEvent(BarometerEvent event) {
return {
"pressure": event.pressure,
"timestamp": event.timestamp.microsecondsSinceEpoch,
};
}
}
28 changes: 28 additions & 0 deletions sdk/python/examples/controls/sensors/accelerometer/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import flet as ft


def main(page: ft.Page):
intro = ft.Text("Move your device to see accelerometer readings.")
reading = ft.Text("Waiting for data...")

def handle_reading(e: ft.AccelerometerReadingEvent):
reading.value = f"x={e.x:.2f} m/s^2, y={e.y:.2f} m/s^2, z={e.z:.2f} m/s^2"
page.update()

def handle_error(e: ft.SensorErrorEvent):
page.add(ft.Text(f"Accelerometer error: {e.message}"))

page.session.store.set(
"accelerometer_service",
ft.Accelerometer(
on_reading=handle_reading,
on_error=handle_error,
interval=ft.Duration(milliseconds=100),
cancel_on_error=False,
),
)

page.add(intro, reading)


ft.run(main)
27 changes: 27 additions & 0 deletions sdk/python/examples/controls/sensors/barometer/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import flet as ft


def main(page: ft.Page):
intro = ft.Text("Atmospheric pressure (hPa).")
reading = ft.Text("Waiting for data...")

def handle_reading(e: ft.BarometerReadingEvent):
reading.value = f"{e.pressure:.2f} hPa"
page.update()

def handle_error(e: ft.SensorErrorEvent):
page.add(ft.Text(f"Barometer error: {e.message}"))

page.session.store.set(
"barometer_service",
ft.Barometer(
on_reading=handle_reading,
on_error=handle_error,
interval=ft.Duration(milliseconds=500),
),
)

page.add(intro, reading)


ft.run(main)
27 changes: 27 additions & 0 deletions sdk/python/examples/controls/sensors/gyroscope/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import flet as ft


def main(page: ft.Page):
intro = ft.Text("Rotate your device to see gyroscope readings.")
reading = ft.Text("Waiting for data...")

def handle_reading(e: ft.GyroscopeReadingEvent):
reading.value = f"x={e.x:.2f} rad/s, y={e.y:.2f} rad/s, z={e.z:.2f} rad/s"
page.update()

def handle_error(e: ft.SensorErrorEvent):
page.add(ft.Text(f"Gyroscope error: {e.message}"))

page.session.store.set(
"gyroscope_service",
ft.Gyroscope(
on_reading=handle_reading,
on_error=handle_error,
interval=ft.Duration(milliseconds=50),
),
)

page.add(intro, reading)


ft.run(main)
27 changes: 27 additions & 0 deletions sdk/python/examples/controls/sensors/magnetometer/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import flet as ft


def main(page: ft.Page):
intro = ft.Text("Monitor the ambient magnetic field (uT).")
reading = ft.Text("Waiting for data...")

def handle_reading(e: ft.MagnetometerReadingEvent):
reading.value = f"x={e.x:.2f} uT, y={e.y:.2f} uT, z={e.z:.2f} uT"
page.update()

def handle_error(e: ft.SensorErrorEvent):
page.add(ft.Text(f"Magnetometer error: {e.message}"))

page.session.store.set(
"magnetometer_service",
ft.Magnetometer(
on_reading=handle_reading,
on_error=handle_error,
interval=ft.Duration(milliseconds=200),
),
)

page.add(intro, reading)


ft.run(main)
30 changes: 30 additions & 0 deletions sdk/python/examples/controls/sensors/user_accelerometer/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import flet as ft


def main(page: ft.Page):
intro = ft.Text(
"Linear acceleration without gravity. "
"Keep the app running on a device with motion sensors."
)
reading = ft.Text("Waiting for data...")

def handle_reading(e: ft.UserAccelerometerReadingEvent):
reading.value = f"x={e.x:.2f} m/s^2, y={e.y:.2f} m/s^2, z={e.z:.2f} m/s^2"
page.update()

def handle_error(e: ft.SensorErrorEvent):
page.add(ft.Text(f"UserAccelerometer error: {e.message}"))

page.session.store.set(
"user_accelerometer_service",
ft.UserAccelerometer(
on_reading=handle_reading,
on_error=handle_error,
interval=ft.Duration(milliseconds=100),
),
)

page.add(intro, reading)


ft.run(main)
14 changes: 14 additions & 0 deletions sdk/python/packages/flet/docs/controls/accelerometer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
class_name: flet.Accelerometer
examples: ../../examples/controls/sensors/accelerometer
---

{{ class_summary(class_name) }}

## Examples

```python
--8<-- "{{ examples }}/basic.py"
```

{{ class_members(class_name) }}
Loading
Loading