Build and tools reference
Every build flag, Flutter command, tool and script you run on the app's source — apply_app_config.dart and its flags, the release preflight and all its check IDs, the iOS identity phase, the installers, run_tests.sh and the icon generator — with what each one reads, writes and prints.
Lookup material. Nothing here is a how-to; for that, see Install and run the app, Build for Android, Build for iOS and Branding.
Run every command on this page from the project root, the folder that
holds pubspec.yaml.
Build flags
The app reads exactly one compile-time flag.
| Flag | Type | Default | Effect |
|---|---|---|---|
STORE_RESTRICTED_MODULES |
bool | false |
true compiles in the Merchant, AI Investment and Affiliate screens. With false their code is left out of the build altogether, not hidden. AI Investment and Affiliate also need the server switch Allow AI Investment & MLM in the Mobile App. See Restricted modules. |
Pass it on any build or run command:
flutter build appbundle --release --dart-define=STORE_RESTRICTED_MODULES=trueThere is no flag for the backend address, the keys, an environment or a
flavour, and the Android project has no product flavours. All of that comes
from assets/config/app_config.json, which is packed into the build as an
asset. See the configuration reference.
The standard Flutter flags that matter for this app:
| Flag | Use |
|---|---|
--release |
Every build you give to anyone. A release build of the app refuses a baseUrl that is not https:// and a wsBaseUrl that is not wss://: the build succeeds, and the app opens on its Configuration Error screen naming the key. Check first with dart run tool/apply_app_config.dart --check --release. |
--build-name=<x.y.z> --build-number=<n> |
Override the version: line of pubspec.yaml for this build. The build number must be higher than any you have uploaded. |
--obfuscate --split-debug-info=<folder> |
Optional. Obfuscates the Dart code. Keep the folder for every release, or its crash traces cannot be read. |
-d <device id> |
Choose the phone or emulator for flutter run. |
Flutter commands
| Command | What it does |
|---|---|
flutter pub get |
Fetches the dependencies. Also writes android/local.properties and ios/Flutter/Generated.xcconfig, which the package does not ship and which Gradle and CocoaPods need. Run it first on a fresh copy. The shipped pubspec.lock needs Flutter 3.38 or newer (Dart 3.10 or newer). |
cd ios && pod install |
macOS only. Installs the iOS pods. Run it after flutter pub get, never before. flutter build ios and flutter build ipa run it for you. |
flutter run |
A debug build on the connected phone or emulator. Debug and profile builds accept http:// and ws:// addresses for a local backend. |
flutter build apk --release |
An APK for testing on phones. Output: build/app/outputs/flutter-apk/app-release.apk. |
flutter build appbundle --release |
The App Bundle you upload to Google Play. Output: build/app/outputs/bundle/release/app-release.aab. Without android/key.properties it is signed with the debug key and Play refuses it. |
flutter build ipa --release |
macOS only. An archive and an .ipa for App Store Connect. See Build for iOS. |
flutter doctor --android-licenses |
Accepts the Android SDK licences. |
dart run build_runner build --delete-conflicting-outputs |
Regenerates the generated code (the JSON models and the dependency-injection wiring). Not needed to build: the generated files ship with the source. Run it only after changing an annotated class yourself. |
A configuration change never needs flutter clean. Stop the app and build
again; a hot restart does not pick up a changed asset.
The web and desktop folders in the package are not targets. The app does not start on the web.
apply_app_config.dart
Makes the build follow assets/config/app_config.json on both platforms, and
prints what each platform will use.
dart run tool/apply_app_config.dart [--check] [--release] [--icons]
[--root <dir>] [--allow-template-id]| Flag | What it does |
|---|---|
| (none) | Validates the config, then writes iosBundleId and iosTeamId, when the config sets them, into ios/Runner.xcodeproj/project.pbxproj. |
--check |
Changes nothing. Exits 1 when the config is invalid, or when the Xcode project's bundle ID or team differs from the config. |
--release |
Also applies the release-build rules: a baseUrl that is not https:// or a wsBaseUrl that is not wss:// is an error. Without it, a warning. |
--icons |
Also regenerates the Android and iOS launcher icons from assets/icons/app_icon.png, after checking it is a real PNG. Ignored with --check. |
--allow-template-id |
Writes iosBundleId even when it is the template's own ID and the Xcode project already has an ID of yours. Refused without this flag. |
--root <dir> |
The project root. Defaults to the current directory. |
--help |
Prints the usage. |
Exit codes: 0 done, 1 something must be fixed, 64 a bad command line.
Only the iOS bundle ID and the Apple team are written, because they are
Xcode build settings that signing reads before any build phase runs. Everything
else is read by the build itself: Gradle reads appName and
androidApplicationId on every Android build, the Apply app_config.json
phase writes appName into every iOS build, and the app reads the colours
when it starts. Run the tool again whenever you change iosBundleId or
iosTeamId, and after taking a new release's Xcode project.
What it checks. Every problem is reported at once, with the same rules the build and the app apply:
| Key | Rule |
|---|---|
baseUrl, wsBaseUrl |
Required. https:// or http://, and wss:// or ws://, with a host. The unencrypted form is an error only with --release. |
appName |
One line: a tab or line break is an error. A warning above 30 characters (Google Play's title limit) and above 12 (about what fits under an iOS icon). |
androidApplicationId |
Two or more dot-separated parts, each starting with a letter, using letters, digits and underscores. |
iosBundleId |
Two or more dot-separated parts of letters, digits and hyphens. A warning when it is the template's own. |
iosTeamId |
Exactly 10 upper-case letters and digits. A warning when it is the template vendor's team. |
primaryColor, buyColor, sellColor, accentColor |
#RRGGBB or #AARRGGBB. An empty value means the default. |
Any of these keys holding a number or true instead of a string in quotes is
an error. The tool does not check the other keys; see the
configuration reference.
What it prints. A summary in four blocks, then warnings, then the next step:
Applied assets/config/app_config.json
Name
in the app "Acme Exchange"
Android home screen "Acme Exchange" (android/app/build.gradle.kts reads appName on every build)
iOS home screen "Acme Exchange" (the "Apply app_config.json" build phase writes it into every build)
Identifiers
Android applicationId com.acme.exchange (androidApplicationId in app_config.json; Gradle reads it on every build, nothing to write)
iOS bundle id com.acme.exchange (iosBundleId; ...)
iOS signing team A1B2C3D4E5 (iosTeamId; ...)
Colours (read by the app at start-up)
primaryColor #2F6BFF custom
buyColor #0ECE7A default
sellColor #FF5A5F default
accentColor #1890FF default
Icon
assets/icons/app_icon.png: 1024x1024 PNG
Next: dart run tool/release_preflight.dartWith --check the first line reads Checked instead of Applied. Read the
Identifiers lines before every release: the first two are the IDs the
stores know your app by, and the third is the Apple team the iOS app is signed
with.
Without --release, an http:// or ws:// address is printed as a warning
starting warning: for a RELEASE build:. With --release it is an error, and
the error block starts
error: assets/config/app_config.json has 1 problem for a release build:.
Release preflight
Reports every way the project is still the template, and the release mistakes that do not stop a build. It builds nothing and changes nothing.
dart run tool/release_preflight.dart # the verdict
dart run tool/release_preflight.dart --explain # the verdict, and the fix for each item
dart run tool/release_preflight.dart --android-only # skip the iOS checks--android-only is for a build that never goes to Apple. It first prints
release preflight: --android-only, so the iOS checks were skipped, then
drops the iOS findings (ios-name-phase-missing, ios-bundle-id-mismatch,
ios-team, ios-team-mismatch, ios-push, and the iOS parts of bundle-id
and launcher-icon). Every Android check still runs. The flags combine.
A passing run prints
release preflight: this build has been differentiated from the template and
exits 0. A failing one prints
RELEASE PREFLIGHT FAILED — N item(s) still ours, not yours:, one line per
finding as [id] what is wrong, and exits 1. Run it before every release
build. The installers run it at the end, as advice only. Why the stores make
this a requirement is on Differentiation.
| ID | Reported when |
|---|---|
config-missing |
assets/config/app_config.json does not exist. |
config-unreadable |
The config is not valid JSON. |
config-invalid |
A value the build or the app would refuse, with the same wording as apply_app_config.dart --check. One finding per problem. |
app-name |
appName is not set, or is still a template name. |
android-label-hardcoded |
AndroidManifest.xml has a typed-in android:label, different from appName, that overrides it. |
android-label-placeholder-missing |
The manifest uses ${appName} but build.gradle.kts is older and never sets it, so every Android build fails. |
ios-name-phase-missing |
The Xcode project has no Apply app_config.json build phase, so the iOS name ignores the config. |
bundle-id |
The Android application ID or an iOS bundle ID is still the template's, or the iOS ID cannot be read from the project. |
android-id-hardcoded |
build.gradle.kts has a typed-in applicationId that overrides androidApplicationId. |
ios-bundle-id-mismatch |
iosBundleId is set and the Xcode project signs under a different ID. Every iOS build would stop. |
ios-team |
The Xcode project still signs with the template vendor's Apple team (52K7QMD7KR, from releases up to 5.3.9), or names no team for Release. The package's Xcode project has no team, so this is reported until you set iosTeamId or choose a team in Xcode. If you build only for Android, use --android-only. |
ios-team-mismatch |
iosTeamId is set and the Xcode project names a different team. |
release-signing |
android/key.properties is missing (the release would be debug-signed), or lacks one of storeFile, storePassword, keyAlias, keyPassword. |
launcher-icon |
A generated launcher icon is missing, or is still the template's. |
ios-push |
ios/Runner/GoogleService-Info.plist exists but the project has no push entitlement, so iOS push cannot work. Not reported when you have not set up Firebase for iOS. |
android-gradle-missing |
android/app/build.gradle.kts cannot be read. |
android-target-api |
compileSdk or targetSdk is missing, inherited from Flutter, or below 36. |
app-icon |
assets/icons/app_icon.png is missing or is still the shipped placeholder, which is text, not an image. |
app-icon-size |
The icon is smaller than 1024x1024. |
palette |
The config sets none of the four colours to a colour of your own. |
backend-url |
baseUrl is still the placeholder https://your-backend-url.com, another address the template ships, or http://localhost. |
backend-not-encrypted |
baseUrl is not https:// or wsBaseUrl is not wss://. A release build would stop on it. |
It deliberately does not judge taste. It checks that each decision was made, not whether it was made well.
ios_app_identity.dart
The script the Apply app_config.json build phase of the Runner target runs on every iOS build. You do not run it yourself, except to preview.
dart tool/ios_app_identity.dart --dry-run [--root <dir>]On each iOS build it:
- writes
appNameinto the builtInfo.plist(CFBundleDisplayNameandCFBundleName), so the trackedios/Runner/Info.plistnever changes; - stops the build with an
error:line wheniosBundleIdis set and differs from the bundle ID Xcode is signing, saying which side to change; - warns, without stopping, when
app_config.jsonis missing or empty and the build falls back to the example.
--dry-run prints what an iOS build would do, compares iosBundleId with the
Xcode project, and exits 1 with
error: [dry run] an iOS build would stop here: ... when a Mac build would
stop. It needs no Xcode and writes nothing, so it works on Windows and Linux.
The iOS build runs this file from tool/. Keep the tool/ folder in the
project, or every iOS build fails.
The installers
setup/installers/install.sh (macOS and Linux) and
setup/installers/install.bat (Windows) walk through a first setup. They find
the project root on their own.
In order, they:
-
Check the tools: Flutter, Dart and Git;
install.shon macOS also checks the Xcode command line tools and CocoaPods, and on Linux, clang.install.shoffers to install what is missing where it can;install.batlists where to download it and stops. -
Write the configuration. If
app_config.jsonexists they ask whether to keep it, create a new one or view it. A new configuration asks, in order: site address, WebSocket URL, app name, Android application ID, iOS bundle ID, the four colours, app version, default exchange provider, default trading pair, Stripe publishable key and Google OAuth client ID. They check each address, ID and colour as you type it, then write a completeapp_config.json. Anhttp://site address orws://WebSocket URL is accepted, for a local test backend, with a warning that a release build refuses it. -
Run
flutter pub get, retrying once afterflutter clean. -
Run
dart run tool/apply_app_config.dart. -
Set up the icon: copy the PNG you name to
assets/icons/app_icon.pngand rundart run tool/apply_app_config.dart --icons. -
Set up the platforms you choose: Android accepts the SDK licences; iOS (macOS only) runs
pod installand reminds you to choose your signing team. A web option is offered; do not use it. -
Run code generation with
build_runner. This is unnecessary, because the generated code ships, and a failure here is harmless. -
Run
flutter doctor, then offer a release APK build.install.shalso offers an iOS build on macOS;install.batalso offers a web build, which does not work. They will not start any of these builds whileapp_config.jsonhas abaseUrlthat is nothttps://or awsBaseUrlthat is notwss://, because the app would stop on its Configuration Error screen; they say so and point you toflutter runfor a debug build. Withoutandroid/key.propertiesthe APK is signed with the debug key: fine on a test phone, refused by Google Play. -
Run
dart run tool/release_preflight.dart --explainas advice, and print the next steps.
Check the file they write before you build for release:
- Choosing "Create new configuration" replaces
app_config.jsoncompletely. Any key the installer does not ask about is reset or gone afterwards: it writes"iosTeamId": "", so a team you had set is cleared, and it leavesgoogleAuthEnabledout. - The Google button follows the client ID you give. With
googleAuthEnabledleft out, the button shows when you entered a Google client ID and stays hidden when you left it empty. If your Android build takes the client ID fromgoogle-services.jsoninstead, add"googleAuthEnabled": true. - The "app version" answer is only a fallback. The version the app shows
comes from the
version:line ofpubspec.yaml. - It writes only keys the app reads. It no longer asks for a CryptoCompare key (market news comes from your server) or about "Coming Soon" features (which modules appear is your server's decision), and it writes none of the keys in Keys the app ignores.
run_tests.sh
The package's test suite, as one command. It is a bash script; on Windows, run
it from Git Bash. It needs flutter on the PATH.
./run_tests.sh # static analysis, then every suite
./run_tests.sh contract # only test/contract
./run_tests.sh --no-analyze # skip static analysis
./run_tests.sh --helpIt runs flutter pub get (the one stage that stops the run on failure),
flutter analyze, then flutter test on each suite in this order:
| Suite | What it covers |
|---|---|
test/core |
The request layer, the config and error mapping. |
test/services |
Service-level parsing. |
test/tool |
apply_app_config.dart, the iOS build phase and packaging. |
test/compliance |
The store-policy gates: differentiation, target API and similar. |
test/features |
The bulk of the suite. |
test/contract |
The app's endpoints checked against the platform's backend source. Without that source next to the project, as in a downloaded copy, these suites skip their checks. |
test/e2e |
The full sign-in stack against a fake server started inside the test. It needs no backend. |
Every stage runs even after one fails. The last lines say ALL GREEN,
GREEN, for what ran (with a NOT RUN: line naming any suite folder that is
missing), or FAILED: with the stages that failed. The exit code is 1 when
anything failed.
The restricted-modules test is written to pass both ways. To check a build with the flag on:
flutter test --dart-define=STORE_RESTRICTED_MODULES=true test/features/dashboard/opt_in_modules_test.dartflutter_launcher_icons
The icon generator, configured in the flutter_launcher_icons: section of
pubspec.yaml. It reads assets/icons/app_icon.png and writes every Android
and iOS launcher icon, and the web icons. It makes no Android adaptive icon:
the android_adaptive_icon: block in that section is not a setting the
generator's version (0.13.1) reads.
dart run flutter_launcher_iconsPrefer dart run tool/apply_app_config.dart --icons. It runs the same
generator, but first refuses the shipped placeholder and warns about an icon
that is not square or is smaller than 1024x1024. Replacing the PNG changes
nothing until one of the two has run; the preflight reports stale icons as
launcher-icon.
What the build prints
Three lines come from android/app/build.gradle.kts on Android builds.
Flutter's normal output filters them out, so look for them in a --verbose
build:
| Line | Meaning |
|---|---|
App identity from assets/config/app_config.json: name="..." applicationId=... |
The name and application ID this build uses. When it names app_config.example.json instead and ends (assets/config/app_config.json is missing or empty, so the tracked template was used), the build read the example config. |
google-services.json found - Firebase push ENABLED / google-services.json not found - Firebase push DISABLED. ... |
Whether this build has Android push. |
WARNING: android/key.properties not found — signing the release build with the DEBUG keystore. ... |
This release build cannot go to Google Play. |
Gradle stops the build, whatever the verbosity, on a config it cannot use:
assets/config/app_config.json is not valid JSON (at line N column M), an
appName or androidApplicationId that must be a string in quotes, an
appName that contains a control character, or an androidApplicationId
that is not a valid Android application ID. More build messages are on
Build for Android and
Troubleshooting.