Flitzdocs
Loader plugins

Android

Add the loader to an Android host with the pl.leancode.flitz.loader Gradle plugin.

The Android loader is a Maven AAR (pl.leancode.flitz:loader-android) paired with a Gradle plugin (pl.leancode.flitz.loader). You apply the Gradle plugin; it adds the AAR to the flavors you choose, checks at build time that your Flitz SDK is new enough for this plugin version, and merges the AAR's manifest contributions. You never declare the AAR dependency yourself.

Before you start, make sure your app builds against the pinned Flitz SDK and that flitz plugins credentials --write has stored the repository token on this machine (see Credentials). In the snippets below, <plugin-version> is the value flitz plugins configure prints for your SDK pin.

Add the Maven repository

Add the authenticated repository to both blocks of android/settings.gradle.kts: pluginManagement resolves the Gradle plugin marker, dependencyResolutionManagement resolves the AAR the plugin pulls in transitively. The credentials reference the flitzMavenToken property that credentials --write stored in ~/.gradle/gradle.properties — nothing secret lands in a committed file.

android/settings.gradle.kts
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
        maven {
            url = uri("https://dl.cloudsmith.io/basic/leancode/flitz/maven/")
            credentials {
                username = "token"
                password = providers.gradleProperty("flitzMavenToken").get()
            }
        }
    }
    plugins {
        id("pl.leancode.flitz.loader") version "<plugin-version>"
    }
}

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("https://dl.cloudsmith.io/basic/leancode/flitz/maven/")
            credentials {
                username = "token"
                password = providers.gradleProperty("flitzMavenToken").get()
            }
        }
    }
}

The Flutter Gradle plugin injects project-level repositories that take precedence over the settings-level ones, so add the same repository block to allprojects in the top-level android/build.gradle.kts as well — otherwise the AAR does not resolve during the app build:

android/build.gradle.kts
allprojects {
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("https://dl.cloudsmith.io/basic/leancode/flitz/maven/")
            credentials {
                username = "token"
                password = providers.gradleProperty("flitzMavenToken").get()
            }
        }
    }
}

Every build needs the token

The plugin is always applied, so every Gradle build of this project resolves it from the private repository — loader-free flavors and CI included. Without flitzMavenToken, Gradle fails while reading the settings, before any flavor is chosen. Run flitz plugins credentials --write on each developer machine. In CI, supply the token as the Gradle property flitzMavenToken, for example through the ORG_GRADLE_PROJECT_flitzMavenToken environment variable (flitz plugins credentials --print-token prints it).

Apply the Gradle plugin and raise minSdk

Apply the plugin unconditionally in android/app/build.gradle.kts. It works only on the app module — the module that applies com.android.application — and fails configuration anywhere else. Do not add an implementation("pl.leancode.flitz:loader-android:…") line — the plugin adds the AAR at its own version.

The loader requires Android 10 (API 29). The Flutter template's minSdk = flutter.minSdkVersion is lower, and the manifest merge of a loader flavor fails until you raise it:

android/app/build.gradle.kts
plugins {
    id("com.android.application")
    id("kotlin-android")
    id("dev.flutter.flutter-gradle-plugin")
    id("pl.leancode.flitz.loader")
}

android {
    // … your existing config …
    defaultConfig {
        // … your existing config …
        minSdk = 29
    }
}

Choose the flavors that carry the loader

The loader attaches per product flavor, through the flitz { } block. Prefer a flavor your project already has (a staging or dev flavor is the usual choice). If the project has no flavors, add a dimension with one flavor that includes the Flitz loader and one that does not:

android/app/build.gradle.kts
android {
    // … your existing config …
    flavorDimensions += "loader"
    productFlavors {
        create("withLoader") { dimension = "loader" }
        create("withoutLoader") { dimension = "loader" }
    }
}

flitz {
    // The loader is active ONLY in the flavors listed here.
    enabledFlavors = listOf("withLoader")
}

enabledFlavors is the only activation control — there is no environment variable, no build-type selector, and no conditional apply. Its rules:

  • Opt-in default. An empty or omitted list activates the loader in no variant. The build logs one flitz.loader: applied but inactive line so an inert plugin is never silent.
  • Matching is on the variant's combined flavorName. With one dimension that is the flavor itself (withLoader); with two dimensions it is the camel-cased combination (freeStaging), never a bare dimension value. The build type (debug/profile/release) is never part of the decision.
  • An unknown name fails the build. An entry that matches no produced variant aborts configuration with a message listing the bad entries and the flavors the project actually declares. This also catches "set enabledFlavors but declared no flavors": a project without productFlavors produces only the empty flavor name.

An inactive flavor's APK is byte-for-byte the same as one built with the plugin never applied: no AAR, no loader activities, no loader permissions, no flitz:// intent filter, none of the loader's transitive dependencies. Absence is the off switch.

Every build of an active flavor also runs an SDK floor check first. It fails the build when the Flutter SDK the build uses is not a Flitz SDK, or is older than this plugin version supports. The message names the fix — see Troubleshooting.

Manifest — nothing to edit

For each active variant, the AAR's manifest merges into yours automatically:

  • pl.leancode.flitz.loader.BundleLoaderActivity — exported, singleTask; takes flitz://download links.
  • pl.leancode.flitz.loader.PanelActivity — exported; takes flitz://flitz-panel links and opens the panel. Without sign-in it closes at once, showing nothing.
  • A sign-in callback activity that takes flitz://flitz-auth links, where the sign-in page returns to the app. Without sign-in it does nothing.
  • pl.leancode.flitz.loader.BundleRunnerActivity — not exported; hosts the bundle's engine.
  • The INTERNET and ACCESS_NETWORK_STATE permissions, and a package query that lets the loader open the sign-in page in a Custom Tab.

Your own MainActivity keeps MAIN/LAUNCHER — unless you launch into the panel — so a home-screen tap still opens your app. Only those three hosts go to the loader; links on your scheme with any other host reach your own activities. The scheme comes from flitz { urlScheme } (default flitz) — see Custom schemes. To open a download link or the panel from your own Kotlin code, see the API reference.

Build and run

Build the flavor that includes the Flitz loader in release mode — a debug host cannot run bundles — and install it on an arm64 device or an x86_64 emulator:

flutter build apk --flavor withLoader --release
adb install build/app/outputs/flutter-apk/app-withloader-release.apk

Then publish a bundle with flitz publish, open the landing page it prints on the device, and tap its button. The loader downloads the bundle over HTTPS and runs it. In a host with sign-in, the first load opens the sign-in page in the browser first. See flitz publish.

Follow the loader log with adb logcat -s FlitzLoader:* flutter:*. A successful load logs Platform dill hash verified, Native package check: … — OK, and finally FlutterView attached.

Where bundles run

All loader activities — the download, sign-in, panel, and bundle screens — run in a separate :flitz process and task. That is what lets a bundle start at any point in your app's life, and it has consequences:

  • Your app keeps running. It stays in its own task with its state, and Back from a bundle returns to wherever the link was opened from — the browser, for example, or the panel when you ran the build from it — not into your app.
  • Your Application class is created again in the :flitz process, and any in-memory singleton exists once per process.
  • ContentProvider-initialized libraries are not initialized there. Android creates a ContentProvider only in the process that declares it, so SDKs that start themselves from one — Firebase, WorkManager, AndroidX Startup, and similar — are not set up in :flitz. Bundle code that needs them initializes them itself, for example with Firebase.initializeApp.
  • Leave the process alone. Do not override android:process on the loader activities, and do not declare a FlutterActivity in the :flitz process. The loader refuses to run with "Cannot run bundles in this process".

Plugins register on the bundle's engine automatically, and their native code runs in the :flitz process.

Next steps

On this page