# Background sync

Canonical page: https://docs.ovok.com/flutter-sdk/background-sync

`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](https://docs.page/fluttercommunity/flutter_workmanager)
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:

```xml
<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:

```dart
@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:

```dart
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 [#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.

```dart
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 [#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:

```dart
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.
