Background sync

Schedule secure offline measurement delivery on Android and iOS.

  • Stable unknown
  • Release candidate since 0.1.0-rc.6
  • Development since 0.1.0-dev.32+1028d01

ovok_flutter schedules the Core offline measurement queue through Workmanager. The client and session stay app-owned; the background task restores them from the host app's configuration and secure storage.

Add workmanager to the app's dependencies when its dispatcher imports the Workmanager API. Follow the Workmanager setup guide for the Android and iOS project configuration. Workmanager requires Android API 23 or later and iOS 14 or later for this integration. iOS scheduling is best effort, may run later than requested, and stops after a user force-quits the app.

On iOS, add the task identifier to BGTaskSchedulerPermittedIdentifiers in ios/Runner/Info.plist. The SDK uses the same value for the Workmanager task name and identifier:

<key>UIBackgroundModes</key>
<array>
  <string>fetch</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
  <string>OvokOfflineMeasurementBackgroundSync</string>
</array>

If UIBackgroundModes already exists, add fetch to its existing array.

Initialize Workmanager once in the app. Its callback dispatcher must be a top-level entry point. Create the handler inside that dispatcher so the callback can restore the client in the background isolate:

@pragma('vm:entry-point')
void callbackDispatcher() {
  final runOfflineSync = registerOfflineMeasurementBackgroundTask(restoreClient);
  Workmanager().executeTask((taskName, inputData) async {
    if (taskName == offlineMeasurementBackgroundTaskName) {
      return runOfflineSync();
    }
    return true;
  });
}

void main() {
  Workmanager().initialize(callbackDispatcher);
  runApp(const MyApp());
}

restoreClient must be a top-level function available to the dispatcher. Rebuild the Ovok client there with non-secret app configuration and the app's secure store. Never put access tokens or credentials in Workmanager task input.

After Workmanager initialization, schedule or cancel recurring delivery:

await scheduleOfflineMeasurementBackgroundSync();
await cancelOfflineMeasurementBackgroundSync();

The default interval is 15 minutes; Android and iOS may run it later. On Android, the task uses a foreground data-sync service with a notification while it runs. runOfflineMeasurementBackgroundSync returns the same queue result as ovok.measurements.offlineQueue.flush().

Local notifications

Add flutter_local_notifications to the host app when it needs these helpers. Initialize its shared plugin before calling them. Disable the plugin's automatic iOS permission prompt so the SDK can request permission only when the app chooses. Configure a notification tap callback there if the app needs to read the JSON payload. Initialize the plugin again inside a Workmanager callback isolate before showing a notification from background work.

When the app uses permission_handler iOS macros, enable PERMISSION_NOTIFICATIONS=1 in the iOS Podfile.

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:ovok_flutter/ovok_flutter.dart';

final notificationsPlugin = FlutterLocalNotificationsPlugin();
await notificationsPlugin.initialize(
  const InitializationSettings(
    android: AndroidInitializationSettings('ic_launcher'),
    iOS: DarwinInitializationSettings(
      requestAlertPermission: false,
      requestBadgePermission: false,
      requestSoundPermission: false,
    ),
  ),
  onDidReceiveNotificationResponse: (response) {
    final payload = response.payload;
    if (payload != null) handleNotificationPayload(payload);
  },
);

final permission = await getPushNotificationPermission();
if (permission == PushNotificationPermissionStatus.undetermined) {
  await requestPushNotificationPermission();
}

await showPushNotification(
  const PushNotificationContent(
    title: 'Measurements synced',
    body: 'Saved measurements were sent to Ovok.',
    data: {'source': 'background-sync'},
  ),
);

Notification data values must be finite numbers, strings, booleans, or null. The helpers do not request permission automatically. Keep notification text and payloads generic because the operating system may show them on a locked device.

Background-safe storage

Wrap the app's secure store when the background task needs its values after the device's first unlock. Configure the app's secure-store implementation with the platform accessibility needed for background reads, then use the same adapter when building the foreground and restored clients:

final storage = BackgroundSafeClientStorage(
  secureStore: appSecureStore,
);

final client = Ovok(
  baseUrl: apiBaseUrl,
  tenantCode: tenantCode,
  secureStore: storage,
);

Call storage.reload() in the background isolate before restoring the session. The adapter tracks only keys written through it; clear() removes those keys. Do not pass credentials through Workmanager task input.

Last updated on

On this page