How Omi's Foreground Task System Enables Background Recording on Mobile
Omi uses the flutter_foreground_task plugin to maintain a persistent Android foreground service that prevents the operating system from terminating the app during background audio recording, while simultaneously fetching location data every five minutes.
The basedhardware/omi repository implements a sophisticated foreground task architecture to ensure uninterrupted audio capture and location tracking on mobile devices. When users minimize the app or lock their screen, standard Flutter applications typically suspend execution, which would terminate ongoing recording sessions. By leveraging a dedicated foreground service defined in app/lib/utils/audio/foreground.dart, Omi maintains an active process that communicates bidirectionally with the main isolate while respecting Android's background execution limits.
Service Initialization and Platform Configuration
The foreground task lifecycle begins in app/lib/pages/home/page.dart, where the HomePage widget initializes the service immediately after rendering its first frame. The initialization sequence checks the platform type, requests notification permissions, and configures Android to ignore battery optimizations.
if (!PlatformService.isDesktop) {
await ForegroundUtil.initializeForegroundService();
await ForegroundUtil.startForegroundTask();
}
Inside app/lib/utils/audio/foreground.dart, the ForegroundUtil class handles platform-specific setup on lines 75–92. This includes requesting the foreground service permission and creating a persistent notification channel required by Android for all foreground services. The utility ensures the service can run indefinitely without being killed by the OS's power management features.
Implementing the Background Task Handler
The core logic resides in _ForegroundFirstTaskHandler, a private class implementing the plugin's TaskHandler interface at lines 17–69 of foreground.dart. This handler defines three critical lifecycle methods that manage the background execution:
onStart: Invoked when the service launches, immediately triggering the first location fetch via_locationInBackground()onRepeatEvent: Executes every 5 minutes (configured viaForegroundTaskEventAction.repeat(60 * 1000 * 5)on lines 33–34)onReceiveData: Processes messages sent from the main isolate back to the background task
class _ForegroundFirstTaskHandler extends TaskHandler {
@override
Future<void> onStart(DateTime timestamp, TaskStarter taskStarter) async {
Logger.debug("Starting foreground task");
_locationInBackground();
}
@override
void onRepeatEvent(DateTime timestamp) async {
Logger.debug("Foreground repeat event triggered");
_locationInBackground();
}
@override
void onReceiveData(Object data) async {
Logger.debug('onReceiveData: $data');
await _locationInBackground();
}
}
Inter-Isolate Communication and Location Updates
The background task fetches geolocation data using Geolocator.getCurrentPosition() and transmits results to the UI isolate via FlutterForegroundTask.sendDataToMain(). This occurs at line 39 of foreground.dart, where the handler serializes location data into a map containing latitude, longitude, accuracy, altitude, and timestamp.
The main isolate receives this data through a callback registered in HomePage.initState at line 53:
FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData);
The _onReceiveTaskData handler parses the location payload and persists it through the app's geolocation service:
void _onReceiveTaskData(dynamic data) async {
if (data is! Map<String, dynamic>) return;
if (!(data.containsKey('latitude') && data.containsKey('longitude'))) return;
await updateUserGeolocation(
geolocation: Geolocation(
latitude: data['latitude'],
longitude: data['longitude'],
accuracy: data['accuracy'],
altitude: data['altitude'],
time: DateTime.parse(data['time']).toUtc(),
),
);
}
Lifecycle Management and Resource Cleanup
Proper lifecycle management prevents resource leaks and unnecessary battery drain. When the HomePage widget disposes, the service stops explicitly at line 30:
ForegroundUtil.stopForegroundTask();
This termination method unregisters the task handler, removes the foreground notification, and releases the wake lock, ensuring the app returns to a normal background state when the user navigates away from the recording interface.
Why Foreground Services Are Required for Background Recording
Android's Doze mode and App Standby buckets aggressively kill background processes to conserve battery. Standard background execution limits prevent apps from accessing the microphone continuously while the screen is off. By promoting the process to a foreground service with a persistent notification, Omi receives the foreground service permission level, granting it CPU priority and exemption from standard background restrictions.
This architecture enables the app to record audio continuously while periodically updating location metadata, even during extended background sessions. The flutter_foreground_task dependency declared in pubspec.yaml, combined with the service declaration in android/app/src/main/AndroidManifest.xml, completes the configuration required for this capability.
Summary
- ForegroundUtil in
app/lib/utils/audio/foreground.dartencapsulates service initialization, permission requests at lines 75–92, and the task handler implementation. - The _ForegroundFirstTaskHandler class executes location updates every 5 minutes via
onRepeatEvent, keeping geolocation metadata current during recording sessions. - Inter-isolate communication uses
sendDataToMain()andaddTaskDataCallback()to transmit GPS coordinates from the background service to the UI layer. - Lifecycle synchronization between
HomePage.initState(lines 91–93) andHomePage.dispose(line 30) ensures the service starts when the app loads and stops when the user leaves the recording interface. - Android foreground service permissions prevent the OS from killing the recording process, enabling continuous audio capture in the background.
Frequently Asked Questions
What plugin does Omi use to manage foreground tasks?
Omi uses the flutter_foreground_task plugin, declared in the pubspec.yaml file, which wraps Android's ForegroundService API and provides Dart APIs for task scheduling and inter-isolate communication.
How frequently does the background task fetch location data?
The task handler triggers location updates every 5 minutes (300,000 milliseconds), configured through ForegroundTaskEventAction.repeat(60 * 1000 * 5) at lines 33–34 of foreground.dart.
Where is the foreground service stopped in Omi's codebase?
The service stops in app/lib/pages/home/page.dart within the dispose() method at line 30, which calls ForegroundUtil.stopForegroundTask() to release resources and remove the persistent notification.
Why does Omi need to request battery optimization exemptions?
Android's Doze mode and background execution limits would otherwise terminate the audio recording process when the screen locks. By requesting the user to ignore battery optimizations in lines 75–92 of foreground.dart, Omi ensures the foreground service maintains CPU priority and microphone access during extended background recording sessions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →