Add docs on 'Create app flavors for Windows and Linux' - #13736
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new documentation guide for setting up Flutter flavors on Windows and Linux desktop apps, along with updating existing docs and navigation links to reference it. Feedback on the new guide highlights a critical MDX syntax error with an unclosed <Tabs> tag, and suggests providing more concrete, actionable CMake and configuration examples for customizing application icons on both Windows and Linux.
| 1. Configure `windows/runner/Runner.rc` or your CMake target | ||
| to use `APP_ICON_NAME` for the application icon resource. |
There was a problem hiding this comment.
This step is currently very vague and does not explain how to configure Runner.rc or the CMake target to use APP_ICON_NAME. Since configuring resource files with dynamic CMake variables can be non-trivial for users, please provide a concrete example or a brief explanation of how to achieve this (for example, using configure_file in CMake to generate Runner.rc from a template).
| 1. Update `linux/CMakeLists.txt` to install the correct `.desktop` file | ||
| and icon based on `FLUTTER_APP_FLAVOR`. |
There was a problem hiding this comment.
|
Staged preview of the updated docs.flutter.dev site (updated for commit 76814d8): https://flutter-docs-prod--docs-pr13736-flavors-windows-linux-xianrw9y.web.app |
|
@AngeloAvv, can you help with a review? The bot is suggesting a more detailed description and I could use some help. |
|
Staged preview of the updated flutter.dev site (updated for commit 7656651): https://flutter-dev-230821--www-pr13736-flavors-windows-linux-6b9jzi0s.web.app |
|
@sfshaza2 sure! I'll take a look in the upcoming days |
|
This PR is holding up other PRs. @AngeloAvv, unless you can get to this soon-ish, I plan to land, because these changes are better than nothing. But in that event, I would still really appreciate if you could either file an issue or a PR with improvements! |
parlough
left a comment
There was a problem hiding this comment.
The fact that there's a set of tabs under most of the sections and a table to visual locations by platform, I'd highly recommend splitting this into two pages. It seems there's not much relating the platforms to each other in the case of flavors, so I think it's best for developers to tackle each separately anyway.
|
|
||
| The following table illustrates the build directories that Flutter creates | ||
| when a project defines two flavors (`staging`, `production`) | ||
| and two build modes (`debug`, `release`): |
There was a problem hiding this comment.
This describes the following table as outlining debug mode as well, but it only includes entries for release.
| when a project defines two flavors (`staging`, `production`) | ||
| and two build modes (`debug`, `release`): | ||
|
|
||
| <table class="table table-striped"> |
There was a problem hiding this comment.
Any reason to not use a Markdown table here?
It can be followed with {:.table .table-striped} to give it the desired styling.
| The value of `appFlavor` matches the string passed to the `--flavor` flag. | ||
| If you run or build without specifying a flavor, | ||
| `appFlavor` returns `null`. |
There was a problem hiding this comment.
This should be updated to account for falling back to the default flavor if it's specified. Perhaps by cross-linking to the "Set a default flavor" section.
| 1. Open `windows/runner/CMakeLists.txt` and pass `FLUTTER_APP_FLAVOR` | ||
| as a preprocessor definition: | ||
|
|
||
| ```cmake title="windows/runner/CMakeLists.txt" |
There was a problem hiding this comment.
This and the content of the other steps should be consistently indented with the rest of their list items/steps.
| For example, a flavor can determine which window title, binary name, | ||
| application ID, API endpoint, asset set, and logging configuration | ||
| applies to a build. |
There was a problem hiding this comment.
"binary name" overpromises. As shipped, the flavor never affects the binary name — the generated templates were not modified by either desktop flavor PR (grep FLUTTER_APP_FLAVOR packages/flutter_tools/templates/ returns nothing), and renaming it manually breaks executable path resolution (see my comment on the CMake section below).
Window title and application ID are achievable, but only as opt-in project-side customizations.
| For example, a flavor can determine which window title, binary name, | |
| application ID, API endpoint, asset set, and logging configuration | |
| applies to a build. | |
| For example, a flavor can determine which window title, | |
| application ID, API endpoint, asset set, and logging configuration | |
| applies to a build. |
| On Windows and Linux, Flutter uses [CMake][] to configure and build | ||
| the native desktop runner. | ||
| When you run `flutter run` or `flutter build` with the `--flavor` flag, | ||
| Flutter provides the flavor name to CMake as the `FLUTTER_APP_FLAVOR` | ||
| variable and isolates the build outputs in a flavor-specific directory. |
There was a problem hiding this comment.
"Flutter provides the flavor name to CMake as the FLUTTER_APP_FLAVOR variable" is accurate but omits the one detail that determines whether any of the examples in this page actually work: where in the CMake evaluation order the variable appears.
It is not a -D command-line argument. It is emitted into flutter/ephemeral/generated_config.cmake with PARENT_SCOPE, and that file is included from <platform>/flutter/CMakeLists.txt. Practically: FLUTTER_APP_FLAVOR is undefined until add_subdirectory(${FLUTTER_MANAGED_DIR}) returns in the top-level CMakeLists.txt.
Stating this up front would prevent the most likely reader mistake — putting the if(DEFINED FLUTTER_APP_FLAVOR) block near the top of the file, where it silently does nothing.
| On Windows and Linux, Flutter uses [CMake][] to configure and build | |
| the native desktop runner. | |
| When you run `flutter run` or `flutter build` with the `--flavor` flag, | |
| Flutter provides the flavor name to CMake as the `FLUTTER_APP_FLAVOR` | |
| variable and isolates the build outputs in a flavor-specific directory. | |
| On Windows and Linux, Flutter uses [CMake][] to configure and build | |
| the native desktop runner. | |
| When you run `flutter run` or `flutter build` with the `--flavor` flag, | |
| Flutter writes the flavor name into `<platform>/flutter/ephemeral/generated_config.cmake` | |
| as the `FLUTTER_APP_FLAVOR` variable, and isolates the build outputs | |
| in a flavor-specific directory. | |
| :::important | |
| `generated_config.cmake` is included from `<platform>/flutter/CMakeLists.txt`, | |
| so `FLUTTER_APP_FLAVOR` only becomes visible in your top-level | |
| `CMakeLists.txt` **after** the `add_subdirectory(${FLUTTER_MANAGED_DIR})` line. | |
| Any `if(DEFINED FLUTTER_APP_FLAVOR)` block placed before that line | |
| is silently skipped. | |
| ::: |
| The following table illustrates the build directories that Flutter creates | ||
| when a project defines two flavors (`staging`, `production`) | ||
| and two build modes (`debug`, `release`): |
There was a problem hiding this comment.
The intro promises two build modes but the table only lists release rows.
| The following table illustrates the build directories that Flutter creates | |
| when a project defines two flavors (`staging`, `production`) | |
| and two build modes (`debug`, `release`): | |
| The following table illustrates the build directories that Flutter creates | |
| when a project defines two flavors (`staging`, `production`) | |
| and builds in release mode: |
| Complete the following steps to configure two flavors called | ||
| `staging` and `production` for a new or existing Flutter project. |
There was a problem hiding this comment.
Worth stating explicitly that this whole section is optional, because unlike Android and iOS, desktop flavors need no project configuration to be functional.
With zero CMake changes, --flavor already gives you:
- isolated build directories (
build/<platform>/<arch>/<flavor>/…) - a populated
appFlavorconstant at runtime - flavor-filtered assets via the
flavors:key inpubspec.yaml default-flavorsupport
CMake configuration is only needed for native differentiation — window title, app icon, application ID. Framing it as a required step 1–3 sequence makes desktop flavors look heavier than they are, and pushes readers into the failure modes flagged below.
| Complete the following steps to configure two flavors called | |
| `staging` and `production` for a new or existing Flutter project. | |
| Passing `--flavor` works on Windows and Linux without any project | |
| configuration: Flutter isolates the build directory, populates | |
| `appFlavor`, and filters flavor-specific assets automatically. | |
| Complete the following optional steps only if you want to differentiate | |
| the *native* runner per flavor — for example its window title, | |
| application ID, or app icon. | |
| This example configures two flavors called `staging` and `production`. |
| 1. Create a new Flutter project called `flavors_example`: | ||
|
|
||
| ```console title="console" | ||
| $ flutter create flavors_example | ||
| $ cd flavors_example | ||
| ``` | ||
|
|
||
| 1. Configure the CMake build files for your target platform: | ||
|
|
||
| <Tabs key="flavors-os" wrapped="true"> |
There was a problem hiding this comment.
Markdown list nesting looks off and will likely break the numbering in rendering.
Item 1 (line 80) uses 1. + 4-space continuation indent, but items at lines 87 and 126 use 1. with no indented body, and the <Tabs> blocks between them start at column 0. That takes the following content out of the list context, so the ordered list restarts rather than continuing 1 → 2 → 3.
Same pattern in the "Create distinct window titles" section (lines 271/280 and 299/308).
Compare with flavors-ios.md, which keeps everything under a consistent 4-space continuation indent. Worth previewing the built page to confirm the step numbers render as 1/2/3.
| 1. Open `linux/runner/CMakeLists.txt` and pass `FLUTTER_APP_FLAVOR` | ||
| as a preprocessor definition: | ||
|
|
||
| ```cmake title="linux/runner/CMakeLists.txt" | ||
| if(DEFINED FLUTTER_APP_FLAVOR AND NOT FLUTTER_APP_FLAVOR STREQUAL "") | ||
| target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_APP_FLAVOR=\"${FLUTTER_APP_FLAVOR}\"") | ||
| endif() | ||
| ``` | ||
|
|
||
| 1. Open `linux/runner/my_application.cc` and update the window title | ||
| in the `my_application_activate` function: | ||
|
|
||
| ```c title="linux/runner/my_application.cc" | ||
| #if defined(FLUTTER_APP_FLAVOR) | ||
| const char* title = "flavors_example (" FLUTTER_APP_FLAVOR ")"; | ||
| #else | ||
| const char* title = "flavors_example"; | ||
| #endif | ||
|
|
||
| if (use_header_bar) { | ||
| GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); | ||
| gtk_widget_show(GTK_WIDGET(header_bar)); | ||
| gtk_header_bar_set_title(header_bar, title); | ||
| gtk_header_bar_set_show_close_button(header_bar, TRUE); | ||
| gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); | ||
| } else { | ||
| gtk_window_set_title(window, title); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
The Linux mechanism here is correct — unlike the Windows tab. GTK takes narrow const char* strings, so the preprocessor define works directly with no conversion. flutter_flavorizr does exactly this, and its example project builds and runs with it.
Two adjustments to match what actually works:
1. Placement. target_compile_definitions(${BINARY_NAME} …) needs the target to exist, so it must come after add_executable(${BINARY_NAME} …). flutter_flavorizr anchors on the existing APPLICATION_ID define, which is already correctly positioned:
# Add preprocessor definitions for the application ID.
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
if(DEFINED FLUTTER_APP_FLAVOR AND NOT FLUTTER_APP_FLAVOR STREQUAL "")
add_definitions(-DFLUTTER_APP_FLAVOR="${FLUTTER_APP_FLAVOR}")
endif()Placing the new block right after the APPLICATION_ID line is an easy instruction to follow and is guaranteed to be in scope.
2. my_application.cc snippet doesn't match the file it's editing. The generated my_application_activate passes the title as a string literal in two places. The snippet introduces a title variable but shows the if (use_header_bar) block dedented to column 0, whereas in the real file it's indented two spaces inside the function — so this can't be pasted as-is.
The working shape, which keeps the diff to the existing code minimal:
const gchar* window_title = "flavors_example";
#ifdef FLUTTER_APP_FLAVOR
if (g_strcmp0(FLUTTER_APP_FLAVOR, "staging") == 0) {
window_title = "Staging App";
} else if (g_strcmp0(FLUTTER_APP_FLAVOR, "production") == 0) {
window_title = "Production App";
}
#endifthen replace the two hardcoded literals with window_title:
gtk_header_bar_set_title(header_bar, window_title);
...
gtk_window_set_title(window, window_title);Using g_strcmp0 rather than string concatenation also lets each flavor have a fully custom title instead of AppName (flavor), which is closer to what people actually want from flavors.
| 1. Prepare your icon files in `.ico` format | ||
| (for example, `app_icon_staging.ico` and `app_icon_production.ico`) | ||
| and place them in `windows/runner/resources/`. | ||
|
|
||
| 1. In `windows/CMakeLists.txt`, select the appropriate icon file | ||
| based on `FLUTTER_APP_FLAVOR`: | ||
|
|
||
| ```cmake title="windows/CMakeLists.txt" | ||
| if(FLUTTER_APP_FLAVOR STREQUAL "staging") | ||
| set(APP_ICON_NAME "app_icon_staging.ico") | ||
| elseif(FLUTTER_APP_FLAVOR STREQUAL "production") | ||
| set(APP_ICON_NAME "app_icon_production.ico") | ||
| else() | ||
| set(APP_ICON_NAME "app_icon.ico") | ||
| endif() | ||
| ``` | ||
|
|
||
| 1. Configure `windows/runner/Runner.rc` or your CMake target | ||
| to use `APP_ICON_NAME` for the application icon resource. |
There was a problem hiding this comment.
Blocking — this section isn't implementable as written.
Two problems:
-
The snippet targets
windows/CMakeLists.txt, whereFLUTTER_APP_FLAVORis still undefined (same scope issue as the earlier section).if(FLUTTER_APP_FLAVOR STREQUAL "staging")on an undefined variable is always false, soAPP_ICON_NAMEsilently falls through toelse(). -
Step 3 — "Configure
windows/runner/Runner.rcor your CMake target to useAPP_ICON_NAME" — is the entire difficulty, and it's left as an exercise.Runner.rcis a resource script; it cannot read CMake variables. There is no way to complete this step from what the page provides.
flutter_flavorizr handles this with the same configure_file pass proposed in my comment on the window-title section — one mechanism covering icon, title, and PE metadata. Extend that block in windows/runner/CMakeLists.txt, before add_executable:
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/Runner.rc.in"
"${CMAKE_CURRENT_SOURCE_DIR}/Runner.rc"
@ONLY
)Rename Runner.rc to Runner.rc.in and parameterize it — these are the exact substitutions its example project ships:
IDI_APP_ICON ICON "resources\\@RUNNER_APP_ICON@"
VALUE "FileDescription", "@WINDOW_TITLE@" "\0"
VALUE "ProductName", "@WINDOW_TITLE@" "\0"
with the per-flavor .ico files sitting in windows/runner/resources/ alongside the default app_icon.ico.
Same caveat as before: Runner.rc becomes a generated file, so it should be gitignored while Runner.rc.in is committed.
| 1. Prepare PNG icons for each flavor | ||
| (for example, `app_icon_staging.png` and `app_icon_production.png`). | ||
|
|
||
| 1. Create corresponding `.desktop` files for each flavor | ||
| (for example, `flavors_example_staging.desktop` and | ||
| `flavors_example_production.desktop`) | ||
| that reference the respective icon and executable binary name. | ||
|
|
||
| 1. Update `linux/CMakeLists.txt` to install the correct `.desktop` file | ||
| and icon based on `FLUTTER_APP_FLAVOR`. |
There was a problem hiding this comment.
This section is prose-only, and step 3 — "Update linux/CMakeLists.txt to install the correct .desktop file and icon" — is where all the actual work lives. A reader can't act on it.
It's also genuinely harder than the Windows case: on Linux the icon isn't embedded in the binary, so it depends on .desktop entries, the icon theme layout, and the packaging format (snap/deb/flatpak).
I'd suggest cutting these three steps and linking to Build and release a Linux app instead, noting that per-flavor icons are a packaging concern rather than a flavor concern. Shipping three non-actionable bullets sets readers up to fail — and I'd rather not propose a concrete recipe here, since I don't have a verified working example to base one on.
| ## Launch a flavor {: #launch-a-flavor } | ||
|
|
||
| After you configure flavors for your Windows or Linux app, |
There was a problem hiding this comment.
Two additions worth making somewhere in this section:
1. --flavor also works with flutter test and flutter drive on desktop, not just run/build. The Linux PR touched commands/test.dart for exactly this, and both devicelab tasks (flavors_test_linux.dart, flavors_test_windows.dart) exercise createFlavorsTest() / createIntegrationTestFlavorsTest(). Readers wiring flavors into CI will look for this.
2. Existing projects shouldn't need to regenerate anything. Neither desktop flavor PR modified the windows.tmpl / linux.tmpl templates, so --flavor works on any existing project as-is — no flutter create . re-run, no template migration. That's a genuinely reassuring detail and cheap to state.
| Replace `<flavor_name>` with the name of your flavor | ||
| (for example, `staging` or `production`). |
There was a problem hiding this comment.
This sentence sits inside the Linux tab, so readers on the Windows tab never see it — even though the Windows snippet above uses the same <flavor_name> placeholder.
Move it out of the <Tabs> block so it applies to both.
Closes #13642
Adds documentation for setting up app flavors on Windows and Linux desktop platforms.