URL scheme
How flitz:// links open bundles, what the QR scanner accepts, and how to use a custom scheme.
The loader opens bundles through two URL forms. flitz publish emits the
download form (as a QR code in the terminal, a --qr-output PNG, and on the
bundle's landing page); the run form is for bundles already on the device.
| URL | Behavior |
|---|---|
flitz://download?url=<https-url> | Downloads the bundle at <https-url> over HTTPS and runs it. The value must be https:// and is URL-encoded inside the url query parameter. |
flitz://run?path=<path> | Runs a bundle already on the device. See the platform difference below. |
flitz://download
The url query parameter must be present, non-empty, and parse as an
https:// URL with a host. Plain http:// is rejected. The download follows
up to five redirects, each of which must also be https://. Progress is shown
in the loader's download screen; a failed download shows the HTTP status and a
retry action.
flitz://download?url=https%3A%2F%2Fexample.com%2Fbuilds%2Fmy_app.flitzflitz://run
The path query parameter must be present and non-empty. What it means
differs by platform:
- Android: an absolute filesystem path the app process can read, for
example a file pushed with
adb pushto/data/local/tmp/. Readability is checked before extraction; an unreadable path shows "Bundle file not found". - iOS: a bare filename inside the app's Documents directory. A value
containing
/, or equal to.or.., is rejected aspath must be a leaf filename in Documents; a filename that does not exist there shows "Bundle file not found".
Recognition rules
The loader identifies its URLs by authority (run or download), never by
scheme. The operating system only delivers URLs for a scheme your app has
registered, so the scheme is already validated by the time the loader sees it —
which is what makes custom schemes work with no code change.
- Authority must be exactly
runordownload(case-insensitive). On iOS, a URL with any other authority is not claimed by the loader and falls through to your own open-URL handling. On Android,BundleLoaderActivitytreats it as a plain launch and opens the scanner. - A URL whose authority is
runordownloadbut whose parameters are malformed is "ours, but invalid" and shows the loader's native error screen ("Invalid bundle URL" on Android, "Invalid URL" on iOS) with the parser's reason. - A new acquisition URL arriving while a bundle is already running tears the
current bundle down (removing its extracted files) and opens the new one.
Android relies on
BundleLoaderActivity'ssingleTasklaunch mode; iOS dismisses the current runner and presents a fresh one. One bundle runs at a time.
QR scanner payloads
The loader's built-in scanner accepts two payload shapes:
| Payload | Behavior |
|---|---|
A run / download URL in your registered scheme | Routed through the same parser as a deeplink. Invalid parameters show a transient error. |
A bare https:// URL | Treated as download?url=<scanned-url>. |
Anything else — another authority, http://, plain text, a malformed URL — is
rejected with an "Unsupported QR code" toast and the scanner stays active.
Custom schemes
flitz is the default scheme, not a requirement. The scheme is set through the
native registration you already write; the loader has no runtime setting for it.
-
Android: set it in the Gradle plugin's block. The plugin feeds the value into the
flitzUrlSchememanifest placeholder of every active variant, which resolves the AAR's<data android:scheme="${flitzUrlScheme}">at merge time. The value must be a valid URL scheme (letter-led; letters, digits,+,-,.); the build fails otherwise.android/app/build.gradle.kts flitz { enabledFlavors = listOf("withLoader") urlScheme = "acme" } -
iOS: change the single value under
CFBundleURLTypes→CFBundleURLSchemesinInfo.plistfromflitzto your scheme. No other key and no Swift change is needed. -
Publishing:
flitz publish --schema acmemakes the emitted QR codes and links use your scheme. The flag is validated as a syntactically valid scheme but not checked against any app; if it does not match what the host registered, the link opens nothing. See flitz publish.
Because the loader only claims the run and download authorities, your app
may share the same scheme for its own deeplinks (for example
acme://profile/42): on iOS those pass through to the running bundle engine —
plugin handlers and the framework navigation channel — and on Android they go to
whichever activity your own intent filters name. The only reserved authorities
are run and download; if you need those two words as hosts of your own
deeplinks, give the loader a distinct scheme.