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:

  1. Add Sankofa and enable Deploy
  2. Ship a base release
  3. Ship your first patch
  4. 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.

bash
npm install -g sankofa-cli
sankofa login
sankofa init --deploy

Run 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):

YAMLsankofa.yaml
app_id: proj_xxxxxxxxxxxxx
api_key: sk_live_xxxxxxxxxxxxx   # filled automatically by sankofa login

lib/main.dart applies any patch staged on the previous run before runApp:

Dartlib/main.dart
// 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:

bash
sankofa doctor

Every 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:

bash
# 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 android

This 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:

Dart
// 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:

Dart
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:

bash
# 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:

Dart
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

  1. Enable Deploy once

    sankofa init --deploy + sankofa login, then sankofa doctor until green.

  2. Release to the store once per version

    sankofa release ios|android builds the binary and captures the base snapshot.

  3. Patch as often as you like

    Edit real Dart behind an interface, run sankofa patch ios|android. Auto-diff does the rest.

Next

Edit this page on GitHub