API-reference topic
API Reference
English | 简体中文
Every static member of FlutterPatcher, and the types they take and return.
These APIs work only in an Android or iOS app built with dart run flutter_patcher build, called a host, because build writes into the app what the device needs to check patches; anything else is in Platform support. How a host loads a patch is in How It Works; the commands that write patch files are in the CLI Reference.
import 'package:flutter_patcher/flutter_patcher.dart';
Overview
| Member | Description |
|---|---|
init |
Configures crash protection; call it before runApp() |
isAvailable |
Whether this app can take patches |
checkUpdate |
Checks a URL for a newer patch file for this host |
applyPatch |
Downloads and installs the file checkUpdate found |
patchTarget |
Identifies this host, for the server in custom delivery |
inspectPatch |
Tells from a file's header whether installing it would change this host |
applyPatchBytes |
Installs a patch file the app fetched itself, as bytes |
applyPatchFile |
Installs a patch file the app fetched itself, from a path on the device |
applyProgress |
Progress of every install |
currentVersion |
The installed patch's seq |
lastBootDiagnostic |
How the latest cold start handled the patch |
blacklist |
Patches blacklisted on this device |
appVersionCode |
The host's build number |
deviceAbi |
The device's primary ABI |
rollback |
Deletes the installed patch on this device |
clearBlacklist |
Empties this device's blacklist |
patchTarget, inspectPatch, applyPatchBytes and applyPatchFile need flutter_patcher 1.1.0 or newer, and isAvailable needs 1.1.1 or newer. Hosts built with flutter_patcher 1.0.0 are patched with flutter_patcher 1.0.0+1; see Upgrading from 1.0.
Methods and properties
init
static Future<void> init({
int maxCrashCount = 1,
List<String> loaderFieldCandidates = const ['flutterLoader'],
bool loaderFallbackHeuristic = false,
Duration verifyAfter = const Duration(seconds: 5),
})
Configures crash protection.
| Parameter | Description |
|---|---|
maxCrashCount |
How many consecutive failed starts of a patch turn it off and blacklist it. A Dart error thrown once the patch's code has started is not counted: the patch is turned off at once; see After a failure. Values below 1 count as 1 |
verifyAfter |
How long after the first frame an uncaught Dart error still counts as a failed start. Only time in the foreground counts |
loaderFieldCandidates, loaderFallbackHeuristic |
Android SO hosts only: how the plugin locates Flutter's loader. Keep the defaults unless you adapt a new Flutter version |
Behavior
- Call it in
main()beforerunApp(), so its error hooks are in place before the first frame. Later calls have no effect. - Native code marks a start successful at its first frame;
initkeeps reporting uncaught Dart errors forverifyAfterafter it. What counts as a failed start and what happens next: Crash Protection.
Throws: nothing.
isAvailable
static Future<bool> get isAvailable
Whether this app can take patches: it runs on Android or iOS and was built with dart run flutter_patcher build. It is false in an app started with flutter run or built with flutter build.
Throws: never.
checkUpdate
static Future<PatchCheckResult> checkUpdate({
String? url,
Duration timeout = const Duration(seconds: 10),
})
Checks url for a newer patch file for this host.
| Parameter | Description | Default |
|---|---|---|
url |
Where to look | The address build wrote into the app: url in flutter_patcher.yaml, or http://127.0.0.1:8686, where serve offers patches, when that is not set |
timeout |
Limit for the connection and the header read | 10 seconds |
Returns: a PatchCheckResult. When there is an update, pass its patch unchanged to applyPatch.
Behavior
- Requests
GET <url>/<fileName>withRange: bytes=0-145, where<fileName>ispatchTarget.fileName, reads the first 146 bytes and judges them asinspectPatchdoes. hasUpdateistrueonly when the file isfresh: a newer patch or a newer withdrawal for this host.- When
isAvailableisfalse, no request is sent and there is no update. 404means no update. Any status other than200,206or404throws, so the server must answer404for a missing file; the full requirements are in CLI Reference: Download server.- The address must be HTTPS, because an unsigned host installs any intact file of its build and a file replaced in transit would run in the app. Plain HTTP is accepted only to the device itself,
http://127.0.0.1,http://[::1]orhttp://localhost, whereserveis reached throughadb reverse. - Redirects are not followed and the address must not contain a user name, a password or a
#fragment, so a response cannot send the download elsewhere.
Throws: PatcherException when no url is given and the app has none, and when the request fails, including a response shorter than 146 bytes.
applyPatch
static Future<PatchApplyResult> applyPatch(
PatchInfo patchInfo, {
void Function(PatchApplyProgress)? onProgress,
})
Downloads the file checkUpdate found and installs it as applyPatchFile does.
| Parameter | Description |
|---|---|
patchInfo |
The PatchInfo checkUpdate returned |
onProgress |
Receives this call's PatchApplyProgress |
Returns: a PatchApplyResult.
Behavior
- A patch takes effect on the next cold start, because the running process keeps its code; a withdrawal deletes the installed patch.
- The download must answer
200with exactly the size the header announced, so an uncompressed response with a differentContent-Lengthfails. - It fails after 15 seconds without data or 2 minutes in total, whatever
checkUpdate'stimeoutwas. Every download failure givesnetwork. - A
PatchInfowithout theurlandsizethatcheckUpdatesets givesinvalidArgs.
Throws: nothing; every outcome is in the result.
final check = await FlutterPatcher.checkUpdate();
if (check.hasUpdate) {
final result = await FlutterPatcher.applyPatch(check.patch!);
if (!result.ok) debugPrint('${result.error?.name}: ${result.message}');
}
patchTarget
static Future<PatchTarget> get patchTarget
Identifies this host. An app that fetches patch files itself sends these values to its server, which picks the file; see Custom Delivery from Your Own Server.
Returns: a PatchTarget.
Throws: PatcherException on an app not built with flutter_patcher.
inspectPatch
static Future<PatchInspection> inspectPatch(List<int> header)
Tells from the first 146 bytes of a file whether installing it would change this host, so the app can skip downloading a file the host would reject.
| Parameter | Description |
|---|---|
header |
The file's first 146 bytes; bytes beyond them are ignored |
Returns: a PatchInspection.
Behavior
- It checks only magic, version, fingerprint and
seq, so installing still verifies the whole file.
Throws: PatcherException on an app not built with flutter_patcher.
applyPatchBytes
static Future<PatchApplyResult> applyPatchBytes(List<int> bytes)
Installs the patch file bytes, or applies the withdrawal it is.
| Parameter | Description |
|---|---|
bytes |
A whole file the CLI wrote for this host build: file in the patch output, or <fileName> in the publication store |
Returns: a PatchApplyResult.
Behavior
- Installs by the rules in What a device accepts, for the next cold start.
Throws: nothing; every outcome is in the result.
applyPatchFile
static Future<PatchApplyResult> applyPatchFile(String path)
As applyPatchBytes, for a file on the device.
| Parameter | Description |
|---|---|
path |
The path of the patch file on the device |
Returns: a PatchApplyResult.
Behavior
- The file stays in place.
- A path that cannot be read gives
ioError.
Throws: nothing; every outcome is in the result.
applyProgress
static Stream<PatchApplyProgress> get applyProgress
A broadcast stream of the progress of every install.
Returns: a stream of PatchApplyProgress.
Behavior
- It carries every phase of
applyPatch, andverifyingandfinalizingofapplyPatchBytesandapplyPatchFile. - Subscribe before installing, because a broadcast stream does not replay earlier events.
Throws: nothing.
currentVersion
static Future<String?> get currentVersion
The installed patch's seq as a decimal string, or null without one.
Behavior
- It changes as soon as an install succeeds, while the patch runs from the next cold start, so until then it describes another patch than
lastBootDiagnostic.
Throws: nothing; a failed read gives null.
lastBootDiagnostic
static Future<PatchBootDiagnostic?> get lastBootDiagnostic
How the latest cold start handled the patch, or null without a record.
Returns: a PatchBootDiagnostic.
Behavior
- A start that loaded a patch is recorded at its first frame; asked earlier, the getter completes then, with this start's record.
Throws: nothing; a failed read gives null.
final diag = await FlutterPatcher.lastBootDiagnostic; // The patch this start runs.
final installed = await FlutterPatcher.currentVersion; // The patch the next cold start runs.
debugPrint('running: ${diag?.status.name} ${diag?.patchVersion}; installed: $installed');
blacklist
static Future<List<BlacklistEntry>> get blacklist
Patches blacklisted on this device.
Returns: a list of BlacklistEntry; empty without any.
Throws: nothing; a failed read gives an empty list.
appVersionCode
static Future<int?> get appVersionCode
The host's build number: Android versionCode, iOS CFBundleVersion.
Throws: nothing; null when unreadable.
deviceAbi
static Future<String> get deviceAbi
The device's primary ABI: on Android the first of Build.SUPPORTED_ABIS, such as arm64-v8a; on iOS ios-arm64.
Throws: nothing; '' when unreadable.
rollback
static Future<void> rollback()
Deletes the installed patch on this device; the next cold start runs the installed version.
Behavior
- The patch is not blacklisted.
acceptedSeqstays, so only a file with a higherseqcan install afterwards.
Throws: nothing.
clearBlacklist
static Future<void> clearBlacklist()
Empties this device's blacklist and resets every patch's failure count.
Throws: nothing.
Types
PatchCheckResult
Returned by checkUpdate.
| Field | Type | Description |
|---|---|---|
hasUpdate |
bool |
Whether there is an update to apply: a newer patch or withdrawal |
patch |
PatchInfo? |
The update; null without one |
PatchInfo
The file checkUpdate found. Pass it unchanged to applyPatch, because a PatchInfo built by hand lacks the fields the download needs and gives invalidArgs.
| Field | Type | Description |
|---|---|---|
version |
String |
The file's seq as a string |
raw |
Map<String, dynamic> |
url, size, seq and action (publish or withdraw) |
PatchApplyResult
Returned by applyPatch, applyPatchBytes and applyPatchFile.
| Field | Type | Description |
|---|---|---|
ok |
bool |
Whether the file was installed or the withdrawal applied; installing the installed file again also succeeds |
error |
PatchApplyError? |
Why it failed; null when ok |
message |
String? |
A description for developers, not written for users |
PatchApplyError
| Value | Meaning | What to do |
|---|---|---|
invalidArgs |
The file's seq is not above acceptedSeq: an older patch or withdrawal, or the same patch after rollback(). With applyPatch, also a PatchInfo that checkUpdate did not return |
Deliver a file with a higher seq: run patch again, or publish --patch <dir> |
blacklisted |
This patch made a start fail and is blacklisted on this device | Deliver a fixed patch |
network |
applyPatch could not download the file |
Retry later |
signatureInvalid |
Not this host's file (another build, flavor, channel or ABI), damaged or truncated, over 320 MiB, or not signed by the host's patch key | Do not retry, because the same file fails the same way; deliver the file named patchTarget.fileName of this build, signed by its key |
ioError |
The file could not be read, or a disk or file system error such as no space | Retry later |
unknown |
Anything else: an unsupported platform, an app not built with flutter_patcher, a patch the host rejects while installing | Report message |
switch (result.error) {
case PatchApplyError.network || PatchApplyError.ioError:
break; // Temporary; check again later.
case PatchApplyError.blacklisted:
break; // This patch failed a start here; wait for a fixed one.
default:
debugPrint('patch rejected: ${result.error?.name} ${result.message}');
}
PatchApplyProgress
Emitted to onProgress of applyPatch and by applyProgress.
| Field | Type | Description |
|---|---|---|
phase |
PatchApplyPhase |
The current phase |
bytesReceived, totalBytes |
int |
Meaningful only while downloading |
fraction |
double? |
Download progress from 0.0 to 1.0; null outside downloading or when the size is unknown |
PatchApplyPhase
| Value | Meaning |
|---|---|
downloading |
Downloading the file; applyPatch only |
verifying |
Verifying the payload digest and signature |
finalizing |
Parsing the patch, installing its resources and committing |
PatchTarget
Returned by patchTarget.
| Member | Type | Description |
|---|---|---|
fileName |
String |
The name of this host's patch files: <version_code>-<abi>.fptc for the default flavor and channel, otherwise <version_code>-<flavor>-<channel>-<abi>.fptc. See Patch files |
versionCode, flavor, channel, abi |
String |
The build, as build recorded it |
strategy |
String |
so or bytecode |
fingerprint |
String |
The host build's fingerprint; files of another fingerprint are rejected |
signed |
bool |
Whether the host accepts only files signed by its patch key. Fixed when the host is built |
acceptedSeq |
int |
The largest seq this host accepted, of a patch or a withdrawal; 0 before any. Only files numbered above it install |
scope |
Map<String, String> |
application_id, version_code, flavor, channel, abi, strategy and fingerprint |
PatchInspection
Returned by inspectPatch.
| Field | Type | Description |
|---|---|---|
fresh |
bool |
true when the header is this host's and its seq is above acceptedSeq |
action |
String? |
publish for a patch, withdraw for a withdrawal; null when the bytes are not a patch file header |
seq |
int? |
The file's sequence; null when the bytes are not a patch file header |
reason |
String? |
Why the file is not fresh: Patch file of another host, Not newer than the accepted patch, or the parse error for bytes that are not a patch file header |
PatcherException
Thrown by checkUpdate, patchTarget and inspectPatch.
| Field | Type | Description |
|---|---|---|
message |
String |
The cause |
PatchBootDiagnostic
Returned by lastBootDiagnostic. Its fields and the meaning of each status are in Crash Protection: Boot diagnostics.
BlacklistEntry
Returned by blacklist. Its fields are in Crash Protection: Blacklist.
What a device accepts
Every install goes through the same native check, whichever API hands over the file, so the delivery channel never decides what runs. The file format is in CLI Reference: Patch files.
| File | Result |
|---|---|
| Over 320 MiB; wrong length, magic, version, host fingerprint or payload digest; or, on a signed host, a missing or invalid signature | Rejected: signatureInvalid |
| A blacklisted patch | Rejected: blacklisted |
A patch with seq above acceptedSeq |
Installed; acceptedSeq becomes its seq. It runs from the next cold start |
| The installed patch file again | Success; nothing changes |
Any other patch with seq not above acceptedSeq |
Rejected: invalidArgs |
A withdrawal with seq above acceptedSeq |
The installed patch is deleted and acceptedSeq becomes its seq. The running process keeps its code; the next cold start runs the installed version |
A withdrawal with seq not above acceptedSeq |
Rejected: invalidArgs |
acceptedSeq is kept per host build, so a new build of the app (a new fingerprint) starts at 0 and the previous build's patch no longer runs.
Platform support
The plugin works only on Android and iOS. On any other platform every call does nothing and prints one warning on first use:
| API | On other platforms |
|---|---|
init, rollback, clearBlacklist |
Does nothing |
isAvailable |
false |
checkUpdate |
Reports no update |
applyPatch, applyPatchBytes, applyPatchFile |
Returns unknown |
patchTarget, inspectPatch |
Throws PatcherException |
applyProgress |
An empty stream |
currentVersion, lastBootDiagnostic, appVersionCode |
null |
deviceAbi |
'' |
blacklist |
An empty list |
On Android and iOS, an app not built with dart run flutter_patcher build accepts no patches: isAvailable is false, checkUpdate reports no update, patchTarget and inspectPatch throw PatcherException, and applyPatch, applyPatchBytes and applyPatchFile return unknown.
Classes
- FlutterPatcher Architecture API-reference Crash-protection
- Flutter hot-update entrypoint for Android and iOS.