Background Sync
Swiftpatch can check for updates and download bundles while your app is backgrounded. Two mechanisms are available — use whichever suits your app:
- Periodic background sync — the SDK wakes up on a schedule (WorkManager on Android,
BGTaskScheduleron iOS). - Silent-push-triggered sync — your backend sends a silent push, the SDK checks + downloads on receipt.
Both require zero extra native code once swiftpatch init has run. For periodic sync, the permissions and identifiers are already in Info.plist and AndroidManifest.xml. For silent push, you wire three lines into your existing push handler.
Periodic background sync
iOS — BGTaskScheduler
swiftpatch init adds the required entries to Info.plist:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.swiftpatch.bg-refresh</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
Under the hood the SDK submits a BGAppRefreshTaskRequest identified by com.swiftpatch.bg-refresh. iOS decides when to actually run it — typically when the device is on Wi-Fi, charging, and not in low-power mode.
iOS gives the task ~30 seconds of CPU time. The SDK uses that window to:
- Call
/sdk/check-update. - If an update is available, download it to the NEW slot.
- Verify signature and hash.
- Leave the slot ready. It applies on the next cold start or resume.
Android — WorkManager
On Android, the SDK schedules a PeriodicWorkRequest via WorkManager with WiFi + battery-not-low constraints by default. No manifest changes required — swiftpatch init added the dependency and the required permissions.
Like iOS, the OS decides when to run the work. Typical cadence is once per 15–60 minutes under favorable conditions.
Triggering from your app
The SDK schedules and un-schedules itself based on configuration. You don't need to call it manually in most cases. Background sync activates when:
- The SDK has been initialized successfully.
- The app has been foregrounded at least once.
- The device meets the OS constraints (Wi-Fi + battery on iOS; WiFi-NOT_REQUIRED + battery-not-low on Android by default).
To explicitly stop background sync (for example, during a user's logout flow), clear state via the tenant-switch API:
await sp.setDeploymentKey(newKey, { clearCurrentBundle: true });
Silent-push-triggered sync
When you ship a critical release, you probably don't want to wait for the OS to decide to wake your app. A silent push will do the trick in seconds.
iOS (APNs)
- Enable the Background Modes → Remote notifications capability in Xcode.
Info.plistalready hasUIBackgroundModes→fetchfromswiftpatch init. Addremote-notification:<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>remote-notification</string>
</array>- In your
AppDelegate, forward silent pushes to the SDK:ios/YourApp/AppDelegate.swiftimport SwiftPatch
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
SilentPushHandler.handleRemoteNotification(
userInfo,
completionHandler: completionHandler
)
} - Send the payload from your backend with
aps.content-available = 1:{
"aps": { "content-available": 1 },
"swiftpatch": {
"event": "check",
"appId": "app_xxx",
"releaseHash": "abc123..."
}
}
SilentPushHandler.parsePayload(...) accepts either the nested swiftpatch object or flat top-level keys (swiftpatch_event, swiftpatch_app_id, swiftpatch_release_hash).
Android (FCM)
Swiftpatch does not take a hard dependency on firebase-messaging. Forward from your existing FirebaseMessagingService:
import com.swiftpatch.SilentPushReceiver
override fun onMessageReceived(message: RemoteMessage) {
if (message.data["swiftpatch_event"] != null) {
SilentPushReceiver.handleData(this, message.data)
return
}
// Your existing handler
}
FCM data-message keys:
| Key | Value |
|---|---|
swiftpatch_event | check or new-release |
swiftpatch_app_id | Your app id (optional verification). |
swiftpatch_release_hash | Target release hash (optional, for hints). |
Which should I use?
| Periodic sync | Silent push | |
|---|---|---|
| Delivery time | Minutes to hours | Seconds |
| OS interference | High — OS decides | Low |
| Requires a push server | No | Yes (APNs + FCM) |
| Good for | Keeping devices current quietly | Critical releases |
| Dependencies | None | APNs certs + FCM |
Many apps run both: periodic sync keeps most users current passively, silent push pokes everyone on the rare critical release.
Telemetry
Background sync fires the same lifecycle events as foreground checks. In your breadcrumb stream (see Integrations) you'll see a type: 'silentPush' or type: 'bgRefresh' tag on the originating event.
Troubleshooting
- iOS periodic task never fires — iOS is extremely conservative. Ensure the device has been on Wi-Fi and charging for several hours. You can force-run the task via Xcode's scheme editor for testing.
- Android periodic task never fires — verify battery saver / Doze isn't suppressing the worker. WorkManager's
PeriodicWorkRequestminimum is 15 minutes; anything shorter is ignored. - Silent push not received on iOS — confirm the payload includes
"aps": { "content-available": 1 }AND you've enabled "Remote notifications" background mode. - Silent push not received on Android — verify
onMessageReceivedis called with data messages (not notification messages). Notification messages are not delivered in the background.
Next steps
- Integrations — forward background-sync events to your crash reporter.
- Channels — background sync respects the device's active channel.
- Events — subscribe to lifecycle events fired from background contexts.