Deploy — Flutter OTA
Getting started
Add Sankofa Deploy to a Flutter app end-to-end — enable Deploy in init, ship a base release to the store, and ship your first over-the-air Dart patch.
This guide takes a Flutter app from zero to a live over-the-air patch. Four moves:
- Add Sankofa and enable Deploy
- Ship a base release
- Ship your first patch
- Confirm it landed on a device
1. Add Sankofa and enable Deploy
Install the CLI, sign in, and let it scaffold the project. sankofa init --deploy is idempotent — safe to re-run and safe to drop into an onboarding script.
npm install -g sankofa-cli
sankofa login
sankofa init --deployRun it in your Flutter project root. It wires up pubspec.yaml, writes sankofa.yaml, installs the pinned Sankofa Flutter runtime, injects the boot calls into lib/main.dart, and patches the Android/iOS native config. The full breakdown of what it writes is in the Flutter SDK — Deploy setup section; the two files that matter most are below.
sankofa.yaml at the project root carries your project keys (filled in by sankofa login):
app_id: proj_xxxxxxxxxxxxx
api_key: sk_live_xxxxxxxxxxxxx # filled automatically by sankofa loginlib/main.dart applies any patch staged on the previous run before runApp:
// ignore: depend_on_referenced_packages
import 'package:dynamic_modules/dynamic_modules.dart';
import 'package:sankofa_flutter/sankofa_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Register the loader + apply any patch staged on the last run.
SankofaUpdater.registerLoader(loadModuleFromBytes);
await SankofaUpdater.preFlight();
runApp(const MyApp());
}preFlight() reads sankofa.yaml, applies a staged patch if one is waiting, and schedules a "patch healthy" confirmation ~10 seconds after the first frame. That confirmation is what arms auto-rollback.
Verify the wiring before you build:
sankofa doctorEvery check should be green. Fix anything red before continuing — a half-wired project produces patches that upload fine but never apply.
2. Ship a base release
The base release is the store binary and the reference snapshot every future patch is diffed against. Bump your pubspec.yaml version first (e.g. version: 1.0.0+1), then:
# iOS — builds a signed .ipa for App Store Connect / TestFlight
sankofa release ios
# Android — builds an .aab (or --apk for a sideloadable build)
sankofa release androidThis produces the store artifact and registers the release under your pubspec.yaml version + build number. That version + build number is the addressing key for every patch you ship against this release, so keep it stable.
Submit the artifact to the stores as you normally would. For iOS there is one Xcode checkbox that silently breaks patch addressing if you leave it checked — read Releasing to the App Store before you upload. See the CLI reference for flavors, code-signing, and symbol-upload flags.
3. Ship your first patch
Here is the whole point of Deploy. Edit real Dart code, then run one command — there is no patch file to author.
Say your base release resolves prices through a service interface:
// lib/pricing.dart — established in the base release
abstract class PricingEngine {
double total({required double subtotal, required int quantity});
}
class DefaultPricingEngine implements PricingEngine {
@override
double total({required double subtotal, required int quantity}) {
// BUG: forgot the bulk discount.
return subtotal * quantity;
}
}Fix the method body in your normal source:
class DefaultPricingEngine implements PricingEngine {
@override
double total({required double subtotal, required int quantity}) {
final gross = subtotal * quantity;
// Fixed: 10% off orders of 10+.
return quantity >= 10 ? gross * 0.9 : gross;
}
}Then ship it:
# Rebuild, auto-diff against the base snapshot, sign, upload.
sankofa patch ios
# Or Android:
sankofa patch android
# Stage a small rollout with a description:
sankofa patch ios --rollout 25 --description "fix bulk-discount pricing"sankofa patch rebuilds your app, diffs it against the base snapshot captured in step 2, and ships only the functions that changed — here, DefaultPricingEngine.total. No hand-authored patch, no version bump, no store review. Rollout defaults to 100%; use --rollout to ramp and --mandatory for a forced apply. Full flags: sankofa patch.
4. Confirm it landed on a device
Devices pick up patches automatically — preFlight() applies a staged patch on the next cold launch, and the SDK checks for new patches on boot and resume. You don't have to write any of that. To surface the state (or drive your own "Update available" prompt), use the SankofaUpdater API:
final updater = SankofaUpdater();
// Which patch is this device running? (null on the baseline release)
final current = await updater.readCurrentPatch();
debugPrint(current?.label ?? 'baseline');
// Is a newer patch available right now?
final result = await updater.checkForUpdate();
if (result.hasUpdate) {
await updater.downloadUpdate(result.update!); // staged; applies next cold launch
}After a patch is downloaded it is staged on disk; the next cold launch applies it through preFlight(). On the dashboard, the release's Patches view shows rollout progress and adoption.
Recap
Enable Deploy once
sankofa init --deploy+sankofa login, thensankofa doctoruntil green.Release to the store once per version
sankofa release ios|androidbuilds the binary and captures the base snapshot.Patch as often as you like
Edit real Dart behind an interface, run
sankofa patch ios|android. Auto-diff does the rest.