flutter_ota_kit 0.2.5 copy "flutter_ota_kit: ^0.2.5" to clipboard
flutter_ota_kit: ^0.2.5 copied to clipboard

PlatformAndroid

Open-source, self-hosted code push (OTA updates) for Flutter Android. Patches Dart AOT libapp.so and assets on cold start with integrity checks and crash rollback.

0.2.5 #

Fixed #

  • pub.flutter-io.cn documentation score: the published archive omitted the doc/ guide files that dartdoc_options.yaml references as documentation categories, so pub.flutter-io.cn's dartdoc pass crashed and awarded 0/10. doc/ is now included in the package archive.
  • Static analysis (pub.flutter-io.cn): fixed the three curly_braces_in_flow_control_structures infos in flutter_ota_kit.dart and ota_progress_overlay.dart that cost 10 analysis points, and pinned the root analysis_options.yaml to package:lints/core.yaml — the exact rule set pub.flutter-io.cn scores against.

0.2.4 #

Fixed #

  • Server-side rollbacks were silently ignored on the device. performSharedUpdateCheck compared the UpdateStatus enum against the string 'ROLLBACK' — always false — so every rollback response was mis-processed: the nil-UUID "fall back to base APK" signal degraded to "up to date" (a disabled bad patch kept running forever), and a rollback-to-older-bundle row degraded to a forward update with the wrong status, bypassing the local rollback-history path in checkAndUpdate. Now compared against UpdateStatus.rollback; locked in by test/update_check_pipeline_test.dart.

  • cohort was dead config on every backend. All five update sources expose a cohort field but never passed it into GetBundlesArgs, so get_update_info always queried with cohort = NULL. Combined with the server rule "NULL cohort is only eligible when rollout >= 1000" (verified against the live is_cohort_eligible SQL), any staged rollout below 100% reached zero devices. The cohort is now threaded from config, and when it is unset the SDK derives a stable per-install cohort from a new native device id (deviceId channel; random UUID persisted in the plugin prefs, seeded through getDefaultNumericCohort). Apps shipping OTA-only to an older base APK keep the historical null-cohort behaviour; devices on a base built with this plugin get automatic cohorts.

  • Forced update could restart the process while the install overlay was still visible. applyUpdate waited a fixed 3 s after end(), but the overlay unmounts only after its minimum display time (up to 7 s). The handle now exposes dismissed, and the restart waits for it (capped at 12 s).

Added #

  • PatcherChannel.deviceId() / native PatcherConfig.deviceInstallId — stable per-install identifier used for cohort hashing (never leaves the device; only the derived 0-999 cohort is sent).
  • test/production_safety_audit_test.dart + test/update_check_pipeline_test.dart — 12 audit/regression tests covering rollback addressability, cohort eligibility, and the update-check pipeline.

0.2.3 #

Fixed #

  • Forced-update overlay progress bar never visibly reached the end. The percentage readout sat in a fixed column beside the bar, reserving ~54px the bar could not grow into, and the readout disappeared as soon as the download phase ended — so during verify/finalize/restart the bar looked stuck just below 100%. The readout now lives on its own line above the bar (right aligned), the bar spans the full panel width, and completing phases display 100% until restart.

Changed #

  • Raised the minimum versions of all direct dependencies to the newest resolvable releases (http 1.6.0, archive 4.3.0, postgres 3.5.17, supabase 2.16.1, package_info_plus 10.2.1, restart_app 1.10.1, asn1lib 1.6.5, mime 2.1.0, path 1.9.1, and floors for args/crypto/plugin_platform_interface).

CLI (flutter-ota 0.1.30) #

  • doctor no longer reports a spurious Supabase returned 401: it probes /rest/v1/ with the service-role key when configured, falling back to /auth/v1/health with the public key. Keyless requests to the PostgREST OpenAPI root are rejected by Supabase's 2025 API-key hardening, so the old keyless probe could never pass.

0.2.2 #

Fixed #

  • App storage ballooning after the first patch (100s of MB). A first code-only (Dart-only) patch was extracting the base APK's entire flutter_assets/ tree even though a code patch never overlays assets. That extraction is now skipped for Dart-only patches. Additionally, extractBaseAssetsIfNeeded no longer leaves an uncompressed mirror of the asset tree on disk alongside its zip — it keeps only the compressed archive (extracted to a temp dir that is deleted), removing a full duplicate copy of every bundled asset.

  • Forced-update overlay polish. Removed the speed / eta / phase stat row: the ETA was derived from a jittery byte-rate and was often wrong, and "phase" duplicated the STEPS list (the "phases jumping at the bottom"). Removed the progress bar's indeterminate sliding animation that ran after the download completed (the line that moved side-to-side during verify/install). The bar is now purely determinate — it fills during download and rests at 100% through verify/install/finalize. Active non-download steps show a steady "working…" instead of a spinning glyph in the status column.

0.2.1 #

Fixed #

  • Forced-update overlay polish. The progress bar is now a real widget that stays on one line (it no longer wraps when the panel is narrow) and animates smoothly toward its target instead of snapping. Steps advance forward-only (no jumping between install/finalize) and activeStep is clamped, so an out-of-order or out-of-range native event can't make the list flicker or throw. Verify/install/finalize now show an indeterminate sliding bar + a live "working" spinner rather than a frozen 0%, so nothing looks hung at initialization or right before restart. On success every step completes and the phase reads "restarting". Download speed is smoothed (EMA), ETA rolls to minutes past 60s, the per-percent log line is throttled to ~every 10%, and the log feed is capped.

Changed #

  • flutter-ota init no longer hardcodes a version. It now runs flutter pub add flutter_ota_kit (falling back to an unpinned dependency) so scaffolded apps always get the latest published SDK.

0.2.0 #

Changed #

  • Single-package consolidation (BREAKING for direct sub-package imports). The former sub-packages flutter_ota_kit_core, flutter_ota_kit_plugin_core, flutter_ota_kit_client, and the flutter_ota_kit_{supabase,postgres, cloudflare,aws,pocketbase} backends are now bundled directly inside flutter_ota_kit (under lib/src/pkg/). Depend on the single flutter_ota_kit package — the separate flutter_ota_kit_* packages are no longer required. The public API (FlutterPatcher, configureSupabase/Postgres/Cloudflare/Aws/ PocketBase, overlay, etc.) is unchanged; only code that imported the internal package:flutter_ota_kit_<x>/... libraries directly must switch to package:flutter_ota_kit/flutter_ota_kit.dart.
  • The CLI (flutter_ota_kit_cli, shipped via the flutter-ota npm wrapper) now depends on the merged flutter_ota_kit package and therefore requires the Flutter SDK to build from source (previously Dart-only).

Fixed #

  • PocketBase getChannels always reported no channels. The PocketBase database plugin queried the dedicated channels collection first and returned on success — but deploy/promote only ever write to bundles, so channels stays empty and the method returned [], making flutter-ota channel list show "(no channels)" even with live bundles. It now treats an empty channels collection as "unused" and falls through to deriving distinct channels from the bundles collection, matching Supabase's bundle-derived get_channels. Added full PocketBase database + storage plugin test suites (20 tests) covering append/get, channel derivation, filtered pagination, getUpdateInfo app-version + fingerprint branching, update/delete, and the storage upload/exists/download/getDownloadUrl/readText/list/delete round-trip, bringing PocketBase to parity with the other backends' coverage.

  • OTA silently reverting hours after a successful update. The Android crash guard's ApplicationExitInfo path (API 30+) charged any recorded REASON_CRASH / REASON_CRASH_NATIVE / REASON_ANR for the last booting pid against the patch — even a crash or ANR that happened long after a healthy boot. On the next cold start the circuit breaker saw that as a boot failure, deleted the patch, and the app reverted to the pre-OTA build. Crash attribution is now bounded to a boot window (PatcherConfig.BOOT_CRASH_WINDOW_MS, 30s): a crash outside that window is treated as a normal runtime failure and no longer reverts a working patch. markBooting now records a boot-start timestamp (boot_started_at) alongside the pid; reset / markBootSuccess manage it accordingly. Added CrashGuardTest covering the boot-window rule.

  • Transient apply failures permanently blacklisted a good patch. applyUpdate blacklisted the patch on any apply failure, including network / ioError / unknown. A single flaky download would pin the device off a perfectly good bundle forever (it's skipped on every later check). Blacklisting is now limited to deterministic failures (md5Mismatch, signatureInvalid, assetPackageInvalid, unsupportedAbi, invalidArgs); transient failures are just retried on the next check.

  • Corrupted / truncated downloads not retried. A payload whose MD5 didn't match returned immediately instead of retrying, so a single CDN blip or truncated body surfaced as a hard failure. MD5 mismatch is now retried with backoff (like a network error), since it's almost always a bad transfer; a signature failure (bytes intact, signature wrong) still fails fast. The streamer also now hard-fails a download whose byte count doesn't match the server's Content-Length, catching silently truncated bodies before install.

  • Cross-origin redirects on the patch URL broke downloads. HttpURLConnection won't auto-follow HTTPS→HTTP or cross-host redirects, so a presigned S3/R2/CDN URL that returns a 30x silently wrote the redirect body as the "patch" (then failing MD5). Redirects are now followed manually (up to 5, resolving relative Location headers).

0.1.15 #

  • Release 2026-09-10

0.1.14 #

  • Release 2026-09-10

0.1.12 #

  • Bump package_info_plus to ^10.0.0.
  • Pana: 160/160 (perfect A+, all 5 categories at full marks including dartdoc coverage).

Chinese version: CHANGELOG-zh.md

0.1.11 #

Added #

  • Built-in forced-update progress UI (zero app code required). A forced update (ServerUpdateResult.shouldForceUpdate) now shows an automatic overlay with a terminal-style dot spinner, a determinate progress bar (when the server reports Content-Length), the live phase label + percentage, and the server OTA message rendered underneath. The consuming app writes no UI.
    • Wrap the app once: runApp(FlutterOtaApp(child: MyApp())).
    • Disable it app-wide with FlutterPatcher.showUpdateUi = false, or FlutterOtaApp(showUpdateUi: false), or by setting MaterialApp.navigatorKey = FlutterPatcher.navigatorKey (fallback host).
    • Only forced updates show the overlay; non-forced updates still stage silently and take effect on the next cold start.

Changed #

  • Forced-update restart now uses package:restart_app (RestartMode.process) instead of the hand-rolled native restart. RestartMode.process performs a true cold restart on Android (exit(0) after relaunching the launch activity), which is required so the patched libapp.so / assets reload. The old native restartApp MethodChannel handler was removed.
  • Monorepo dependency hygiene. All intra-monorepo path: dependencies were converted to hosted version constraints, with dependency_overrides pointing back at the local paths. This keeps dependencies publishable (no invalid_dependency analyzer warnings) while local development still resolves in-repo copies — no need to publish untested packages.
  • SDK alignment to the latest stable. Dart floor raised to >=3.13.2 and the plugin's Flutter floor to >=3.47.2 across the root and every sub-package.

Fixed #

  • FlutterOtaApp now roots the app in an Overlay so its OverlayState is actually discoverable by the SDK (the previous Overlay.of lookup ran from an ancestor context and silently returned null, so the overlay never appeared in the zero-code path).

0.1.9 #

Changed #

  • Shared the update-check orchestration across all backends. The (previously copy-pasted) check() body in SupabaseUpdateSource, PostgresUpdateSource, CloudflareUpdateSource and AwsUpdateSource is now a single backend-agnostic performSharedUpdateCheck() in lib/src/shared_update_check.dart. Each backend keeps only its own config building + plugin factory calls; everything else (bundle-id normalization, appVersion/fingerprint argument building, the getUpdateInfo call, download-URL resolution and ServerUpdateResult mapping) lives in exactly one place. Behavior is unchanged. Added flutter_ota_kit_plugin_core as a direct dependency (for the shared DatabasePlugin / StoragePlugin types).

Fixed #

  • Runtime appVersion auto-detection now applies to Postgres, Cloudflare and AWS too, not just Supabase. Those three sources had the same silent-failure bug described in 0.1.8 (they reported a hardcoded/empty APP_VERSION and the backend dropped the bundle), but only the Supabase source was fixed in 0.1.8. resolveAppVersion() (in lib/src/app_version_resolver.dart) is now the single shared resolver used by all four sources.
  • resolveAppVersion() no longer caches an empty detection forever. A transient PackageInfo.fromPlatform() failure previously cached '' and permanently pinned the reported version to empty, silently breaking appVersion-strategy targeting on every later check. It now only caches a successful, non-empty detection and retries on the next check.

0.1.8 #

Fixed #

  • Clients now auto-detect their real app version, so OTA updates no longer silently fail. Previously the generated flutter_ota_kit_setup.dart defaulted APP_VERSION to the hardcoded string '1.0.0'. When a bundle was deployed with a different --target-app-version (e.g. 1.0.1), the client reported 1.0.0, the backend's filterCompatibleAppVersions dropped the bundle (semverSatisfies('1.0.1','1.0.0') is false), and the app stayed "up to date" forever — no update, no error. SupabaseUpdateSource now resolves the version from the host app's versionName via package_info_plus whenever SupabaseUpdateConfig.appVersion is null or empty, and the flutter-ota init generator now writes an empty default (which triggers detection) instead of '1.0.0'. An explicit --dart-define=APP_VERSION=… / SupabaseUpdateConfig.appVersion still wins.

0.1.7 #

Fixed #

  • Added the missing android.os.Process / kotlin.system.exitProcess imports for the forced-update restart (Process.killProcess, Process.myPid, exitProcess). The 0.1.6 upload missed these, so the Android native build failed to compile.

0.1.6 #

Fixed #

  • Forced-update restart now works reliably on modern Android. The previous AlarmManager-based relaunch is deferred or dropped in Doze / battery-saver modes, so the app stayed closed. It now uses startActivity with FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK followed by a Handler.postDelayed(200ms) + Process.killProcess, which is the same mechanism used by restart_app (RestartMode.process) and is not affected by Doze. The developer does not need to change any Dart code — forced updates restart automatically during checkAndApplyUpdates / init.

0.1.5 #

Fixed #

  • Forced updates now actually relaunch the app. handleRestartApp used to startActivity(NEW_TASK) and then killProcess(myPid()) after 200ms — but a NEW_TASK launch still runs in the same process, so the kill took down the just-launched activity and the patched native lib never loaded. It now schedules the relaunch via AlarmManager (outside the process) and then System.exit(0), so the app reopens automatically with the update applied.

0.1.4 #

Fixed #

  • Fixed the Android build failing on host apps that use AGP 9 built-in Kotlin, with Failed to apply plugin 'kotlin-android' followed by project ':flutter_ota_kit' does not specify compileSdk. This affects projects created with or migrated to Flutter 3.44, which ship AGP 9. android/build.gradle now checks whether built-in Kotlin is actually active on the host and applies the Kotlin Gradle Plugin, plus the matching jvmTarget DSL, only where that DSL exists: kotlinOptions when KGP is applied, kotlin { compilerOptions } under built-in Kotlin.

    The switch is built-in Kotlin, not the AGP major version. AGP 9 hosts that opt out with android.builtInKotlin=false (the default the Flutter 3.44 template generates) still need the plugin to bring its own KGP, exactly like AGP 8 hosts, and keep the previous code path unchanged.

    This is a build-time only change, with no runtime behavior change. Patches remain fully compatible: payloads produced by or for 0.1.3 install and boot identically on 0.1.4, and no repack is required.

  • No change to the minimum supported Flutter or AGP version. Existing hosts on AGP 8 build exactly as before.

Known issues #

  • On AGP 9 hosts, Flutter still prints WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): flutter_ota_kit. The Flutter Gradle Plugin decides this by text-matching android/build.gradle, so it cannot see that the apply plugin line is guarded. The warning is safe to ignore, and the literal has to stay: when the Flutter Gradle Plugin finds no KGP declaration in a plugin, it applies kotlin-android to that plugin itself, which fails the build under built-in Kotlin.
  • Flutter 3.44's own Gradle plugin does not support android.newDsl=true and fails to apply before any plugin is evaluated. Keep the template default android.newDsl=false.

0.1.3 #

Added #

  • Added Android cold-start Flutter asset hot updates. Assets (images, fonts, JSON, anything reachable via Image.asset(...) or rootBundle.load(...)) can be patched together with Dart code through the same patch.zip payload.
  • Added --assets to dart run flutter_ota_kit:pack. Pass paths inline (--assets a,b) or read them from a UTF-8 text file with the @ prefix (--assets @patch-assets.txt, one path per line, # starts a comment); inline paths and @file references can be mixed in the same flag. Each path must already be registered under assets: in the new APK's pubspec.yaml; --assets only tells pack which of those assets to ship inside patch.zip. The runtime overlays them on top of the APK's Flutter asset bundle at install time.

Changed #

  • dart run flutter_ota_kit:pack now always emits dist/patch.zip + dist/manifest.json (outer schemaVersion: 2, payload: patch.zip), whether or not --assets is passed. A Dart-only patch.zip contains just manifest.json + lib/<abi>/libapp.so; its inner manifest omits the assets block. The previous bare-.so output mode is gone.
  • Android runtime detects ZIP payloads, installs overlay asset packages, builds a private flutter_assets.apk, and starts Flutter through a patched FlutterJNI AssetManager when assets are present. Dart-only patch.zip payloads short-circuit the asset overlay pipeline and behave like code-only patches at install time.
  • mock_server --dist reads manifest.payload and serves the declared file.

Compatibility #

  • Bare-.so patches produced by 0.1.0-0.1.2 still install on 0.1.3 devices (the runtime keeps a quiet legacy install path); the producer CLI no longer emits that format. Server operators should ship patch.zip for any new patch built against a 0.1.3+ host APK.

0.1.2 #

Added #

  • Added dart run flutter_ota_kit:mock_server for local checkUpdate -> applyPatch testing without maintaining an example-only helper script.

Changed #

  • Improved README onboarding with a TL;DR, clearer fit / non-fit guidance, store policy warning, and local mock server instructions.
  • Updated pub.flutter-io.cn package description and topics for better discoverability.
  • Added a GitHub social preview image under doc/social-preview.png.

0.1.1+1 #

Fixed #

  • Corrected the README install snippet version pin to ^0.1.1 (docs-only, no code change).
  • Translated CHANGELOG to English so pub.flutter-io.cn's pana check no longer flags it for non-ASCII content. Chinese version preserved as CHANGELOG-zh.md.

0.1.1 #

Changed #

  • PatchInfo.md5 is now optional. An empty string means the caller explicitly opts out of download integrity verification and relies on HTTPS only. When md5 is empty the Ed25519 signature check is also skipped (the signature input is the md5 hex, so no md5 means no signature input). toJson omits the md5 key when it is empty.
  • validatePatchArgs: blank md5 is now accepted; non-blank md5 is still required to be 32 lowercase hex chars.
  • Blacklist: when the caller does not provide md5, the download pre-check falls back to version-only matching via the new BlacklistStore.containsByVersion. Blacklist entries are still recorded with the actual md5 computed after download.
  • meta.json: effectiveMd5 now always stores the md5 computed after download (previously it stored the server-declared md5). Boot checks and blacklist entries key on this stable hash.
  • Dependency constraints relaxed: Dart SDK constraint changed from ^3.10.7 to >=3.0.0 <4.0.0; runtime dependencies switched to a lower bound plus an open upper bound; archive now supports both 3.x and 4.x to reduce host-project conflicts.

0.1.0 #

First public release (Android-only beta).

Core features #

  • Cold-start hot updates: replaces FlutterLoader.findAppBundlePath via reflection inside Application.attachBaseContext, before the Dart engine starts, enabling whole-file libapp.so replacement.
  • Signature verification: built-in Ed25519 (X.509 SubjectPublicKey Info) plus MD5 dual verification, with strictSignature mode that prevents downgrade bypass on older devices.
  • Crash circuit breaker / auto rollback: counts REASON_CRASH events from ApplicationExitInfo and hooks PlatformDispatcher.onError on the Dart side. Once maxCrashCount (default 1, fail-fast) is reached, the patch is deleted, added to the blacklist, and the host falls back to the bundled APK version.
  • First-frame verify clears the breaker: after the patch loads, the app must stay alive in the foreground for verifyAfter (default 5s) before being marked verified, which resets the crash counter.
  • Local blacklist: auto-blacklisted patches will never be reinstalled, preventing crash loops. Inspect or clear via FlutterPatcher.blacklist / clearBlacklist.
  • Progress event stream: FlutterPatcher.applyProgress exposes downloading / verifying / finalizing phase events.
  • CLI packaging tool: dart run flutter_ota_kit:pack extracts libapp.so from a release APK and produces the patch manifest.

Known limitations #

  • Android only. On iOS / Web / desktop, all APIs are no-ops (the first call prints a warning).
  • Strict Ed25519 mode requires Android API 33+. Below API 33 with strictSignature: true (the default), signed patches are rejected.
  • Only full-mode patches are supported. Differential patching is not shipped in 0.1.0 to avoid exposing an unverified path.
  • This initial release shipped the legacy lib-only payload path. Asset payloads were added later in 0.1.3.

Documentation #

  • Repository README: use cases, 5-minute demo, integration steps.
  • doc/architecture.md: native + Dart layered architecture and startup sequence.
  • doc/api-reference.md: full API reference.
  • doc/crash-protection.md: breaker and rollback strategy.
0
likes
160
points
601
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

Open-source, self-hosted code push (OTA updates) for Flutter Android. Patches Dart AOT libapp.so and assets on cold start with integrity checks and crash rollback.

Repository (GitHub)
View/report issues

Topics

#flutter #android #code-push #hot-update #ota-update

License

MIT (license)

Dependencies

archive, args, asn1lib, crypto, flutter, http, mime, package_info_plus, path, plugin_platform_interface, pointycastle, postgres, restart_app, supabase

More

Packages that depend on flutter_ota_kit

Packages that implement flutter_ota_kit