Skip to main content

Complete Usage Patterns Matrix

The tk_ota_push SDK and shorebird.yaml permit developers to combine trigger mechanisms (native auto-update, silent Gatekeeper, critical forced Gatekeeper, manual checks), restart behaviors, Wi-Fi constraints, distribution tracks (stable / staging), and security policies (TLS pinning / APK anti-repackaging).

Comparison Table of 8 Integration Patterns​

#Integration Patternshorebird.yaml (auto_update)enableGatekeeperforceUpdaterequireWifiChannel (track)Pinning & ShieldPatch Application
1Silent Eco-Data with Gatekeeper (Production Standard)falsetruefalsefalsestableActiveNext Cold Start
2Silent Wi-Fi Only & Battery >= 30%falsetruefalsetruestableActiveDeferred (workmanager) then Cold Start
3Critical Forced Update (Blocking Modal)falsetruetrueIgnoredstableActiveImmediate download + restart prompt
4Autonomous Native Rust Auto-Update (Zero Dart Code)true(N/A)(N/A)NostableRSA signature (strict)Next Cold Start
5100% Manual Control ("Check for Updates" Button)falsefalse(N/A)NostableOptionalOn demand
6Dynamic Staging Track Switch (Internal QA Menu)falsetrue / falsefalsefalsestagingOptionalImmediate on next boot
7High Security: TLS Pinning + Anti-Repackaging + Anti-Rootfalsetruefalse / truefalsestableStrict (HttpOverrides + APK SHA-256)Blocked if device/network untrusted
8Without Gatekeeper (enableGatekeeper: false) with SDK Policiesfalsefalsefalse / truetrue / falsestableActiveAlways queries server

Code Examples for Each Combination​

In shorebird.yaml: auto_update: false. The OTA server is only queried when all 3 RemoteConfigGatekeeper conditions are met. The patch is downloaded silently in the background and applied on the next cold start without disrupting user sessions.

final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
enableGatekeeper: true,
requireWifiForSilentUpdate: false,
minBatteryPercentageForSilentUpdate: 20,
);

final outcome = await ota.syncWithRemoteConfig(
const OtaRemoteConfig(
targetReleaseVersion: '1.0.0+3',
patchAvailable: true,
latestPatchNumber: 2,
forceUpdate: false,
),
);
// outcome.executionMode == OtaUpdateExecutionMode.silentBackgroundUpdate
// outcome.restartRequired == false (applied on next Cold Start)

Pattern 2: Silent Wi-Fi Only & Background Task Scheduler​

Ideal for user bases on constrained cellular data plans, deferring downloads until the device connects to unmetered Wi-Fi with at least 30% battery.

final scheduler = InMemoryOtaBackgroundScheduler(); // Or custom workmanager implementation

final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
enableGatekeeper: true,
requireWifiForSilentUpdate: true,
minBatteryPercentageForSilentUpdate: 30,
environmentMonitor: const DefaultOtaEnvironmentMonitor(
defaultConnectivity: OtaConnectivityType.mobile,
defaultBatteryPercentage: 80,
),
backgroundScheduler: scheduler,
);

final outcome = await ota.syncWithRemoteConfig(
const OtaRemoteConfig(
targetReleaseVersion: '1.0.0+3',
patchAvailable: true,
latestPatchNumber: 2,
forceUpdate: false,
),
);
// outcome.executionMode == OtaUpdateExecutionMode.deferredToBackgroundTask
// outcome.networkCallPerformed == false

Pattern 3: Critical Forced Update with Blocking Modal & Restart​

When forceUpdate: true arrives in the Remote Config payload, the SDK bypasses Wi-Fi restrictions, fires onCriticalUpdateRequired, and sets outcome.restartRequired = true upon download completion.

final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
enableGatekeeper: true,
requireWifiForSilentUpdate: true, // Automatically ignored when forceUpdate == true
onCriticalUpdateRequired: (config, triggerImmediateDownload) async {
// Show non-dismissible modal (barrierDismissible: false)
final success = await triggerImmediateDownload();
if (success) {
// Prompt user to immediately restart application
}
},
);

final outcome = await ota.syncWithRemoteConfig(
const OtaRemoteConfig(
targetReleaseVersion: '1.0.0+3',
patchAvailable: true,
latestPatchNumber: 3,
forceUpdate: true,
releaseNotes: 'Mandatory critical security hotfix.',
),
);
// outcome.executionMode == OtaUpdateExecutionMode.criticalForcedUpdate
// outcome.restartRequired == true

Pattern 4: Autonomous Native Rust Auto-Update (auto_update: true, without Gatekeeper)​

When operating without a Remote Config provider and wishing for the C++/Rust engine to poll for updates on every application launch:

# shorebird.yaml
app_id: "com.myapp.mobile"
base_url: "https://ota.myapp.internal"
channel: "stable"
auto_update: true
patch_verification: strict
patch_public_key: MIIBCgKCAQEA...

In main.dart, you can still leverage TkOtaPush for rollback recovery and support code retrieval:

void main() async {
WidgetsFlutterBinding.ensureInitialized();
final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+1',
onRollbackDetected: (event) async {
// Clean local cache following native engine rollback
},
);
await ota.verifyStartupAndHandleRollback();
runApp(const MyApp());
}

Pattern 5: 100% Manual Control Without Remote Config​

Triggers an update check only when the user clicks "Check for Updates" in app settings (auto_update: false in shorebird.yaml):

Future<void> onManualUpdateButtonPressed(BuildContext context) async {
final ota = TkOtaPush.instance;
if (!ota.isAvailable) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('OTA engine not available in debug mode.')),
);
return;
}

final status = await ota.checkForUpdate();
if (status == UpdateStatus.outdated) {
await ota.update();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Update downloaded. Please restart the app.')),
);
}
} else if (status == UpdateStatus.restartRequired) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('A patch is already staged. Please restart the app.')),
);
}
}

Pattern 6: Staging Track vs Stable Switching (QA Mode)​

Enables internal QA testers running production binaries (stable) to switch to staging on-the-fly to validate canary patches before public promotion:

final ota = TkOtaPush.instance;

// Enable staging channel
ota.enableQaMode();
assert(ota.track == UpdateTrack.staging);

// Download patch published with --channel staging
final downloaded = await ota.checkAndUpdate();

// Revert to stable channel
ota.disableQaMode();
assert(ota.track == UpdateTrack.stable);

Pattern 7: TLS Certificate Pinning, APK Anti-Repackaging & Root Detection​

Combines TLS pinning on the Dart HTTP stack (HttpOverrides.global) with OtaSecurityPolicy in TkOtaPush.init:

import 'dart:io';
import 'package:tk_ota_push/tk_ota_push.dart';

void initHighSecurityOta() {
const pinnedCertHash = '42d889f8f4b6bf9d3abb767d3b5f215e0009e4d26cb7b958497e8b291e1b96dc';

// 1. Enforce TLS certificate pinning on HttpClient
HttpOverrides.global = OtaPinnedHttpOverrides(
OtaCertificatePinner(
pinnedSha256Fingerprints: {pinnedCertHash},
enforcedHosts: {'ota.saramoney.internal'},
),
);

// 2. Initialize TkOtaPush with APK verification and root/jailbreak detection
TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
securityPolicy: const OtaSecurityPolicy(
blockRootedOrJailbrokenDevices: true,
expectedApkSignaturesSha256: {
'6b6929c8ff345cf008496328d7bff081a8ccfb89498bf9916ed668be3c3dedcf',
},
pinnedServerCertSha256Fingerprints: {pinnedCertHash},
),
);
}

Pattern 8: Without Gatekeeper (enableGatekeeper: false) with Shield and Scheduler​

Retains the mobile security shield, rollback tracking, and battery checks while bypassing the 3 Gatekeeper version/patch conditions (GatekeeperVerdict.bypassedGatekeeperDisabled):

final ota = TkOtaPush.init(
appReleaseVersion: '1.0.0+3',
enableGatekeeper: false, // Bypasses version/patch number conditions
requireWifiForSilentUpdate: true,
);

final outcome = await ota.syncWithRemoteConfig(
const OtaRemoteConfig(
targetReleaseVersion: '',
patchAvailable: false,
latestPatchNumber: 0,
forceUpdate: false,
),
);
// outcome.gatekeeperEvaluation?.verdict == GatekeeperVerdict.bypassedGatekeeperDisabled