flutterplaza_code_push 0.1.6
flutterplaza_code_push: ^0.1.6 copied to clipboard
Over-the-air code push updates for Flutter apps. Check for updates, download patches, and roll back — all at runtime.
0.1.6 #
-
crash fix (the big one): moves the stale-patch cleanup out of Dart and into iOS native code. 0.1.3/0.1.4/0.1.5 all tried to clean up inside
CodePush.init(), which runs insidemain()— but on the reported crash pathmain()never runs. The custom Flutter engine loadspatch.vmcodeas the isolate's snapshot data during isolate initialization, beforeDart_InvokeMainis ever called; if the on-disk patch is incompatible with the running baseline, the VM aborts withSIGABRTinsideDN_Internal_loadDynamicModuleand the process dies without executing a single line of Dart.- 0.1.6 converts
flutterplaza_code_pushinto a Flutter plugin with iOS native code. A new Objective-C class registers a+loadmethod that runs during dyld image loading — beforemain()in the ObjC entry point, beforeUIApplicationMain, before the Flutter engine is instantiated, before any Dart code runs. This is the earliest hook available to a Flutter plugin. - The
+loadcleanup computes<NSDocumentDirectory>/code_push_patches/patch.vmcode, compares its mtime against the max ofRunner.app/RunnerandRunner.app/Frameworks/App.framework/App(so the check works for native-only rebuilds, Dart-only rebuilds, and full builds), and deletes the patch plus its sibling files (boot_counter,launch_status.json,patch_info.json,patch.vmcode.tmp) if the bundle is newer. Steady-state runs where the patch is newer than the bundle are a no-op. - Apps upgrading from 0.1.5 get automatic CocoaPods integration on their next
flutter pub get+cd ios && pod install. NoAppDelegateormain.dartchanges required. Users who previously added the 0.1.5 Dart-sideCodePush.init(...)as the first line ofmain()can leave it there — it's now redundant for crash prevention but still configures the SDK normally. - The unreachable Dart-side
_cleanupStalePatchSyncis removed. Its doc comment was correct about the crash mechanism but wrong about the fix layer; keeping dead code that can't possibly fire is worse than deleting it. - Non-iOS is unchanged. Android's engine uses a different load path and does not hit this crash.
- 0.1.6 converts
-
API improvement — config reuse:
CodePushOverlayalready callsCodePush.init(...)internally from itsinitState, which meant apps that wrapped their root widget inCodePushOverlayand also calledCodePush.init(...)at the top ofmain()were double-initializing with identical config. 0.1.6 fixes the duplication three ways:CodePush.initstores its config in a newCodePush.lastConfigstatic field on every call.CodePushOverlay.configis now optional. When omitted, the overlay falls back toCodePush.lastConfig— so apps that want to configure at the top ofmain()can now writeCodePushOverlay(child: ...)without repeating every field. An explicitconfig:on the overlay still wins for cases where the overlay needs different settings.CodePush.initandCodePushConfigboth makeserverUrloptional now, defaulting to the newCodePush.defaultServerUrlconstant (https://api.codepush.flutterplaza.com). Apps targeting the production service only need to supplyappIdandreleaseVersion.- Typical new shape:
…or, if you prefer one call site:void main() { CodePush.init( appId: Platform.isIOS ? '...ios-app-id...' : '...android-app-id...', releaseVersion: '1.2.0+15', ); runApp(CodePushOverlay(child: MyApp())); }
Both work.void main() { runApp(CodePushOverlay( config: CodePushConfig( appId: Platform.isIOS ? '...ios...' : '...android...', releaseVersion: '1.2.0+15', ), child: MyApp(), )); }serverUrlcan still be overridden explicitly if you're pointing at a self-hosted server.
-
breaking-ish: this release adds a CocoaPod dependency to your iOS build. On first
flutter pub getyou'll need to runcd ios && pod install(or let Flutter do it on the next build). If your app already hasflutter_compile-built code push working,pod installshould be a no-op beyond adding theflutterplaza_code_pushpod itself.
0.1.5 #
- fix:
_cleanupStalePatchSyncin 0.1.4 comparedpatch.vmcodeagainstPlatform.resolvedExecutable, which on iOS resolves toRunner.app/Runner— the thin Objective-C / Swift shell. Flutter's incremental iOS build only rewritesRunnerwhen native code changes; a pure Dart rebuild (includingfcp codepush patch --build) updatesRunner.app/Frameworks/App.framework/App(the AOT snapshot) but leavesRunneralone. Result: stale patches from previous rebuilds were never detected because the Runner mtime stayed frozen in the past while the AOT blob and the patch both advanced. The crash scenario that 0.1.4 was supposed to prevent still fired. - The fix uses the max of
Runner.app/RunnerandRunner.app/Frameworks/App.framework/Appas the bundle freshness proxy. Whichever is newer represents "when the current code was last touched", so the comparison stays robust across native-only rebuilds, Dart-only rebuilds, and full builds. Missing files are tolerated gracefully (use the other; skip cleanup if both are missing). The derivation uses onlydart:io, so the cleanup still runs synchronously before any microtask yield.
0.1.4 #
- fix: the 0.1.3 compatibility guard only covered
CodePush.checkAndInstall(fresh downloads). It did not protect against a stalepatch.vmcodefile that was already on disk from a previous install — the Flutter engine's boot flow schedules a dynamic-module load of that file during isolate initialization, before the SDK'scheckAndInstallruns. On iOS, that load firedSIGABRTinsideDN_Internal_loadDynamicModulewith a release-mode VM assert and no user-visible diagnostic when the stale patch was incompatible with the newly-installed app binary (the Documents directory is preserved across app updates, but the.appbundle is replaced). The engine's existing three-strike auto-rollback (kMaxBootAttempts = 3on the native side) still eventually recovered the app, but the user experienced three crash loops before the rollback fired. - The fix is a new synchronous
_cleanupStalePatchSync()that runs at the very top ofCodePush.init(), before any microtask can drain. It usesPlatform.environment['HOME']anddart:ioonly (no method channels, no FFI, nopath_provider) so nothing yields. It compares the patch file's mtime againstPlatform.resolvedExecutable's mtime, and deletes the patch (plus theboot_counter,launch_status.json,patch_info.json, andpatch.vmcode.tmpsiblings) if the app binary is newer than the patch — the signal that the patch was written by a previous install. Steady-state users whose patch is newer than the binary are unaffected; the cleanup is a no-op. Non-iOS is a no-op (the Android engine has its own in-process recovery and does not crash in this scenario). - Required: call
CodePush.init(...)at the very top of yourmain(), beforerunApp()and before any otherawait. The cleanup only works if it runs before the Dart isolate's event loop starts draining microtasks. - Safety net: if you miss the
init()call, or if the cleanup fails for any reason (e.g. unusual sandbox permissions), the engine's built-in three-strike auto-rollback will still recover the app on the fourth boot after the upgrade — ugly UX but not a brick.
0.1.3 #
- security / fix: refuse to apply a patch when the running Flutter engine has no code push support, instead of loading it into the VM and crashing. Previously, if a device's baseline build was made with a stock Flutter engine (or any engine whose ABI disagreed with the patch's build environment), the SDK called into a runtime hook that does not exist on that engine, which manifested as a release-mode
EXC_BAD_ACCESS (SIGSEGV)insideDRT_AllocateObjectreading from0x10— an unrecoverable crash with no diagnostic. The fix:CodePush.hasCodePushEngine(new, public) — async getter that probes theflutter/codepushmethod channel with a 2s timeout. Apps can use it to hide update UI on incompatible baselines.- Internal two-phase fingerprint probe:
- Phase 2 (when the engine exposes a
CodePush.getEngineAbimethod channel handler) — ABI-level comparison against the server'sengine_fingerprintfield. - Phase 1 (fallback for older engines) — presence check via the existing
CodePush.getReleaseVersionhandler. Treats "engine present" as sufficient and skips the ABI comparison.
- Phase 2 (when the engine exposes a
CodePush.checkAndInstallruns the probe before writing any patch bytes to disk or calling intodart:ui. If the probe returns null, the patch is rejected with a structured status message and a best-effort telemetry POST to/api/v1/telemetry/client-errorso publishers can see how many devices are stranded on incompatible baselines.- New
IncompatibleBaselineException(inmodels.dart) carryingreason,expectedFingerprint,actualFingerprintfor apps that want to handle the rejection explicitly.
- chore: suppress the analyzer's
undefined_functionwarning onui.codePushLoadModulewith a narrowly-scoped// ignorecomment, since the hook is provided dynamically at runtime by the custom engine and cannot be resolved statically. No behavior change.
0.1.2 #
- fix: safe type handling for server responses (prevents crashes from malformed data)
- fix: broad exception catch on all platform channel calls
0.1.1 #
- fix: license updated to BSD 3-Clause (was incorrectly MIT)
- fix: README license reference corrected
- add:
.pubignoreto exclude build artifacts from pub.dev archive - add:
topicsandissue_trackerto pubspec.yaml for discoverability
0.1.0 #
- Initial release
CodePush.checkForUpdate()— check server for available patchesCodePush.downloadAndApply()— download and install a patch (with progress callback)CodePush.currentPatch— get info about the active patchCodePush.isPatched— check if running with a patchCodePush.rollback()— remove the active patchCodePush.installPatch()— install a patch from raw bytesCodePush.releaseVersion— get the baseline release versionCodePush.cleanupOldPatches()— free disk spaceCodePush.checkForUpdatePeriodically()— periodic background checksCodePush.patchCount— number of stored patches