This document explains how state is handled in the app, with emphasis on provider responsibilities and data flow.
The app uses provider with a mixed approach:
- Global
ChangeNotifierproviders for app-wide state. - Plain
Providerfor stateless services. - Feature-local providers created on demand (for per-device/per-sensor state).
- Streams from
open_earable_flutterbridged into provider state.
Main provider wiring starts in lib/main.dart.
File: lib/view_models/wearables_provider.dart
Responsibilities:
- Source of truth for connected wearables.
- Creates and stores one
SensorConfigurationProviderper wearable. - Tracks stereo pair combine/split UI preference.
- Emits high-level streams for:
- unsupported firmware events
- wearable events (time sync, errors, firmware update availability)
- Handles capability updates and sync side effects (for example time synchronization).
Used by:
- Devices page
- Sensor configuration pages
- Sensor page orchestration
- Mini-app device selectors
File: lib/view_models/sensor_recorder_provider.dart
Responsibilities:
- Tracks recording session state (
isRecording, start time, output directory). - Manages per-wearable/per-sensor
Recorderinstances. - Starts/stops recording streams for all connected sensors.
- Handles recorder setup for wearables that connect during an active recording session.
Used by:
- Recorder tab UI
- App lifecycle logic in
main.dartto prevent shutdown while recording
Provided globally in main.dart (type comes from open_earable_flutter).
Responsibilities:
- Holds FOTA selection/update context used across warning/select/update screens.
Used by:
- FOTA flow widgets under
lib/widgets/fota/
File: lib/view_models/app_banner_controller.dart
Responsibilities:
- Stores active transient
AppBannerentries. - Provides methods to show/hide banners.
Used by:
GlobalAppBannerOverlaymain.dartevent handling (maps wearable events to banners)
File: lib/models/wearable_connector.dart
This is provided with Provider.value (not ChangeNotifier).
Responsibilities:
- Encapsulates direct
WearableManagerconnection calls. - Emits connect/disconnect events as a stream.
Used by:
- Device connect UI
main.dartglobal event wiring
Provided as ChangeNotifierProvider.value in main.dart.
Responsibilities:
- Owns logging file operations and state for log browsing screens.
File: lib/view_models/sensor_configuration_provider.dart
Created by WearablesProvider per connected wearable.
Responsibilities:
- Tracks selected config values.
- Tracks pending (optimistic) edits until hardware reports matching values.
- Tracks last reported device configuration snapshot.
- Resolves whether a configuration is selected/applied/pending.
Consumption pattern:
SensorConfigurationViewpulls provider instances fromWearablesProvider.- It passes each via
ChangeNotifierProvider.valueinto row/detail widgets.
File: lib/view_models/sensor_data_provider.dart
Created and owned by SensorPage as a map keyed by (Wearable, Sensor).
Responsibilities:
- Subscribes to sensor stream (
SensorStreams.shared(sensor)). - Maintains rolling time-window queue for charting and value displays.
- Uses throttled notifications for UI smoothness.
- Handles stale/silent stream behavior so charts age out correctly.
Consumption pattern:
- Passed down to live data cards/details using
ChangeNotifierProvider.value.
- Connection succeeds via
WearableConnectoror auto-connect. main.dartreceives event and calls:WearablesProvider.addWearable(...)SensorRecorderProvider.addWearable(...)
WearablesProvidercreates/updates per-deviceSensorConfigurationProviderand subscriptions.- UI consumers (
Consumer,watch,read) rebuild where needed. - Sensor pages create
SensorDataProviderinstances for available sensors and stream live updates.
- User modifies selections in
SensorConfigurationProvider(local/pending state). - UI marks pending values.
- User taps "Apply Profiles".
SensorConfigurationViewsends selected values to hardware (config.setConfiguration(...)).- Provider stream receives hardware report; pending entries are cleared when values match.
For stereo pairs, mirrored target entries can be applied alongside primary entries.
App lifecycle handling in main.dart coordinates provider state:
- Uses
SensorRecorderProvider.isRecordingto decide whether shutdown should be deferred. - Uses
WearablesProvider.turnOffSensorsForAllDevices()when close-shutdown setting is enabled. - Uses
AutoConnectPreferences+BluetoothAutoConnectorto pause/resume reconnection policy.
SharedPreferences:- auto-connect toggles and remembered names
- app shutdown/graph settings
- File storage:
- sensor profile JSONs via
SensorConfigurationStorage - log files via
LogFileManager
- sensor profile JSONs via
Providers remain in-memory runtime state; persistence is delegated to model/storage helpers.
When introducing new state:
- Put cross-screen runtime state in a top-level provider.
- Keep feature-specific transient state near the widget tree that owns it.
- Keep persistence out of widgets; use model/storage helpers.
- Prefer stream-to-provider adapters over direct widget stream subscriptions.
- Use
ChangeNotifierProvider.valueonly for existing provider instances (already-created objects).