Flitzdocs
Concepts

How it works

The host, the bundle, and the loader — and what happens between publish and run.

The Introduction gives the short version: Flitz splits your app into a host and a bundle, and flitz publish gets a fresh bundle onto a teammate's device in seconds. This page is the long version. Every capability Flitz has, and every limit it has, follows from that one split — so it is worth knowing what sits on each side of the line.

For development and testing

Flitz runs in your development and test loop only — your production build ships the normal way and is never touched.

Host and bundle

A normal Flutter build compiles everything — your code, the Flutter framework, the Dart platform libraries, the engine, your native plugins — into one artifact. Flitz draws a line through that artifact. Everything heavy or native goes on one side and is built once; everything that changes from commit to commit goes on the other and is published as often as you like.

The host: your app + the loader

The host is usually your own app with the Flitz loader embedded: a normal, signed build of your app, produced from the Flitz SDK your project pins, in release mode, and installed on a device the way any build of your app is. It carries everything that is compiled:

  • The Flitz loader — the Android or iOS library you embed. It fetches a bundle, checks that it fits, and runs it.
  • The Dart runtime and platform libraries — dart:core, dart:async, dart:ui, dart:io, dart:ffi and the rest — compiled ahead of time, and able to run bundle code too.
  • The Flutter engine — rendering, layout, text, platform channels.
  • Your native plugins and FFI libraries — compiled in at build time, exactly as in a normal build.

Because the host is built from your app, it inherits your native dependencies. That one fact does a lot of work later on: it is why a bundle can use every plugin you already have, and why it can use only those.

The bundle: a complete build of your app's Dart side

The bundle is what flitz publish produces — a .flitz archive containing:

  • Your app's Dart code and every Dart package it uses, including the Flutter framework, compiled to portable bytecode. A bundle carries the full framework, not a reduced subset — what runs is real Flutter. It also means even a small app's bundle is a few megabytes.
  • Your assets — images, fonts, shaders — packaged the way a normal Flutter build packages them.
  • A manifest — a small file stating what the bundle needs from a host: the Flitz SDK it was built with and the packages with native code it depends on. The loader reads it before running anything.

Two things a bundle is not. It is not a patch or diff against some released version — it is the whole app, which is why any branch, commit, or experiment can be a bundle, with no dependency on what was published before it. And it contains no native code — nothing the host does not already have.

The trade in one sentence

Portable bytecode on one side; the runtime, engine, and native code on the other, compiled once and shared by every bundle. Swapping bundles swaps your app's code and assets while the runtime stays put.

From publish to run

The workflow has three steps. The first happens once; the other two are the everyday loop.

Embed the loader — once

Add the Flitz loader plugin to your Android and iOS projects, build the host with the Flitz SDK your project pins, and install it on the devices your team reviews on. The host must be a release build — a debug build cannot run bundles.

You rebuild the host only when its native side changes: a new or upgraded package with native code, or a new SDK pin. Day-to-day Dart changes never require it.

See Loader plugins for the Android and iOS setup, or Getting started for the end-to-end walkthrough.

Publish a bundle

In your project directory, run flitz publish. It compiles your app's Dart code to bytecode, packages it with your assets, and uploads the result in one go.

While compiling, it checks that your code uses only the platform APIs a bundle can reach. A statically typed call outside that boundary fails the build on your machine, not on a device. On SDKs with Dart 3.13 or newer, a dynamic call into the platform libraries is checked when it runs; older SDKs do not check dynamic calls.

When it finishes, flitz publish prints a link to a small landing page and a flitz://download?url=… deeplink, ready to hand to a teammate. It records the publish in your organization's history under an app (your pubspec name: by default) and a track (your Git branch by default), and also prints the track's link, which always opens the newest build on that track. Links have no expiry date: they work as long as the publish and its app exist. By default anyone who holds one can download the bundle; on Flitz Enterprise, an app set to Members only opens its builds only in a host with sign-in, for a signed-in member of your organization. See Apps and tracks for apps, tracks, links, and download access, and flitz publish for flags and machine-readable output.

Open it on a device

A teammate — a designer, product owner, QA, or another developer — opens the link on a device that has the host installed, usually by opening the landing page and tapping its button. The flitz:// link opens the host, the loader downloads and checks the bundle, and the app runs. What they see is the real app: full framework, assets, platform channels, and native plugins, with your latest changes in it.

On Flitz Enterprise, a host built with sign-in first signs the teammate in with their Flitz account, and lets them pick an app, a track, and a build in the panel, inside the app.

Laid out end to end:

From publish to runOn the developer machine, editing app code leads to flitz publish, which uploads a bundle to Flitz hosting and prints a landing-page link, a deeplink, and the track's link. On the teammate's device, opening the link opens the host, which downloads the bundle, runs the compatibility check, and then runs the app.DEVELOPER MACHINEFLITZ HOSTINGTEAMMATE'S DEVICEEdit app codeany branch, any commitflitz publishcompiles + uploadsLanding page + linkflitz://download?url=…Bundleapp.flitzHost opensyour app + the loaderCompatibility checkdoes it fit this host?App runson the Flitz engineprintsuploadsopens linkdownloads

The two halves of the loop are the uploader (flitz publish, producing a bundle and a link) and the downloader (the loader inside the host, fetching and running it). The check in the middle is why a bundle runs only on a host whose SDK and native packages match it — see The compatibility contract.

Inside the loader

When a flitz://download link opens the host, the loader downloads the bundle over HTTPS (see URL scheme) — a host with sign-in signs the user in first. A download can be refused before any check runs: Members only means the build's app lets only signed-in members of its organization download it, and Can't open builds with this account means the signed-in account belongs to another organization. The loader then checks that the bundle fits the host and runs it. A failing check refuses the bundle before any of its code runs, and the error screen's title says which side to fix:

  • Bundle invalid — the bundle does not fit this host: it was built with a different SDK, or it needs a package with native code the host lacks.
  • Loader needs a newer SDK — the host was built with an SDK older than its loader plugin supports. Rebuild the host with a newer pin.
  • Loader build is broken — the host build itself is wrong, for example it was not built with the Flitz SDK. Rebuild the host.

See Loader troubleshooting for each message and its fix.

Where the bundle runs

Where a bundle runs differs by platform, and both ways matter for your app:

  • Android — the bundle runs in its own process and task, next to your app. Your app keeps running in its own task, with its state; Back from the bundle returns to wherever the link was opened. Your Application class is created again in the bundle's process, and SDKs that initialize through a ContentProvider — Firebase, WorkManager, AndroidX Startup, and similar — are not initialized there; a bundle that uses them initializes them itself.
  • iOS — the loader takes over your app's window and shuts down your app's own Flutter engine. Your app's UI does not come back until you relaunch the app. Native state set up at launch, such as an SDK configured in your app delegate, stays in place.

On both platforms the bundle runs with your app's full privileges and storage. Anything it writes — preferences, files, database rows, keychain entries — stays after a restart, and your host build reads it too. Relaunching the app starts it on the code the host was built with, or on the panel if your host launches into it; the loader does not reopen the last bundle on its own. Use a dedicated preview build, or clear its data, when a bundle changes stored data in ways the host build does not expect.

What runs compiled, what runs interpreted

Your app's Dart code and the framework's Dart layer run in the Dart VM's bytecode interpreter. Everything else — the platform libraries, rendering, layout, text shaping, platform channels, and your native plugins — stays compiled. Calls cross between the two automatically. There is no special API, no annotation, and no "Flitz mode" to write against: the same source that compiles to a normal Flutter build compiles to a bundle.

The consequence to plan for is speed. Pure-Dart computation runs roughly 10–50× slower than in a release build, while rendering stays fast. A bundle is right for reviewing UI and flows, not for measuring performance.

The compatibility contract

A host runs a bundle only if all of the following hold. The loader checks them before running anything and refuses a mismatch with an error that names it.

What must matchIf it does not
The Flitz SDK. The host was built with, and the bundle published with, the same pinned SDK.Platform dill hash mismatch — rebuild the host with the pinned SDK.
Native packages. The host contains every package with native code — plugins and FFI packages alike — in the bundle's dependencies, direct or transitive, whether or not the bundle calls it.Bundle requires native packages not available in this host — add the package to the host, then rebuild and reinstall it.
Loader plugin and SDK. The host's loader-plugin version supports the SDK the host was built with.The host build fails, or the loader shows "Loader needs a newer SDK" — use the plugin version flitz plugins configure prints.
Bundle format. The CLI that published the bundle and the loader plugin in the host read the same bundle format.Unsupported bundle version — update whichever side is older.

In practice this means three things for your team:

  • Pin one Flitz SDK and use it on both sides. Build the host with it and publish with it. flitz sdk manages the pin.
  • Build the host from the same pubspec.yaml you publish from, and rebuild it when you add or upgrade a package with native code. The loader compares package names, not versions, so an upgraded plugin's native side reaches the device only with a host rebuild.
  • Build the host in release mode. A debug host runs no bundles.

Nothing ties a bundle to one host: the compatibility check looks at the SDK and the native packages, not at which host or app a bundle comes from. Within that envelope, publish as many builds as you like — any branch, any commit, any experiment — without touching the host.

Fits is not the same as safe

The compatibility check guarantees that a bundle fits its host. It does not make a bundle safe, and nothing in the loader checks where a bundle came from. A bundle runs with the full privileges of the host — the same permissions, the same data, the same network access. In a host with sign-in, that includes the signed-in teammate's stored Flitz session, with their name and email. Flitz is for your own team's bundles: run only bundles you trust as much as your own app's code. Untrusted third-party bundles are not supported.

Constraints

These follow directly from the design above, and they are the questions a sharp evaluator asks first — so here they are up front.

  • The host must be a release build. A debug build of your app cannot run bundles.
  • Interpreted Dart is slower. Pure-Dart computation runs roughly 10–50× slower than in a release build. Do not profile or benchmark through Flitz.
  • No hot reload. Flitz does not change your inner loop on your machine; it changes how your team sees work. Hot reload remains your IDE's job.
  • Host and bundle must share the Flitz SDK and the native packages. The loader enforces this automatically; keep the pin and the pubspec.yaml consistent and rebuild the host when either changes.
  • A bundle cannot add or change native code. It can only use the plugins and FFI libraries the host already has. Needing a new native plugin means a new host build.
  • Bundles are for your own team. The loader does not check where a bundle came from; the compatibility check decides fit, and trust is your call. A bundle can read everything the host can, including a signed-in teammate's Flitz session.
  • A bundle writes to your app's storage. What it stores stays after a restart; relaunching the app returns to the host's own code.
  • Dev and test only. Flitz never touches your production build, which ships the normal way. Flitz is not code push, not over-the-air updating, and not a way to update a published consumer app — whether that would even be compatible with store policy is an open question, and Flitz makes no claim about it. The clearly supported use is internal: your team, your app, during development.

Where to go next

On this page