MMashDiv

Make it your app — name, IDs, colours and icon

One file, assets/config/app_config.json, sets the app's name, its Android and iOS identifiers and its four brand colours; one image sets the icon. What each key does, what each platform does with it at build time, what changing it costs, and how to move an older hand-edited copy onto it.

17 min readUpdated 26 September 2026mobile, branding, app-name, application-id, bundle-id, colours, icon, preflight

Your app's name, its store identifiers and its brand colours are keys in one file, assets/config/app_config.json. The icon is one image, assets/icons/app_icon.png. You do not edit AndroidManifest.xml, Info.plist, build.gradle.kts, the Xcode project or app_themes.dart to brand the app: the build reads app_config.json and carries your values onto both platforms.

It is built this way because the old way failed quietly. Releases up to 5.3.9 kept the name in three places — the config (the name inside the app), AndroidManifest.xml (the Android home screen) and Info.plist (the iOS home screen). An operator who edited only the config, as the configuration guide said, shipped an app that used their name on every screen and "BiCrypto" under its icon, under the template's package id, in the template's colours.

androidApplicationId and iosBundleId are what Google Play and the App Store identify your app by. Neither store lets an app change its identifier after the first release: a new identifier is a new app. The name and the colours can change whenever you like.

One file, and how it is read

  • Edit assets/config/app_config.json, never the example. The package ships app_config.json as a copy of assets/config/app_config.example.json. The example is only a fallback: the app and the Android build use it when app_config.json is missing or empty, so an edit made there does nothing while your own file exists.
  • Every key on this page is optional. A key you leave out keeps the template's value, so a file without them builds exactly the app the template builds.
  • An empty value counts as left out, for every key here, colours included. "appName": "" gives the default name, not a blank one. Spaces around a value are ignored.
  • The installers write these keys for you. setup/installers/install.sh and install.bat ask for the name, both identifiers and the four colours, write them into app_config.json, and then run dart run tool/apply_app_config.dart for you — exactly what you do by hand after editing the file. They do not ask for a Team: they write "iosTeamId": "", so set it yourself before an iOS release. See Install.

The rest of the file (backend address, sign-in options, payment keys) is on App configuration, and every key is listed in the configuration reference.

The keys

Key When absent or "" Format What it sets Takes effect
appName BiCrypto One line of text. A tab or line break stops the build. The name inside the app, and the name under the icon on the Android and iOS home screens The next build, on both platforms. Nothing else to run.
androidApplicationId com.bicrypto.mobile Two or more parts separated by dots; each part starts with a letter and uses only letters, digits and underscores The Android app's identity on the phone and on Google Play The next Android build. Nothing else to run.
iosBundleId Whatever the Xcode project already has (the template's is com.bicryptto.mobile). The example file ships it as "". Two or more parts separated by dots, using only letters, digits and hyphens. No underscores. The iOS app's identity on the phone and on the App Store After you run dart run tool/apply_app_config.dart. An iOS build stops if you skip it.
iosTeamId Nothing is written: the Xcode project keeps its own Team. The template has none, so you choose yours in Xcode. The example file ships it as "". Your Apple Developer Team ID: exactly 10 characters, upper-case letters and digits The Team the iOS app is signed by (DEVELOPMENT_TEAM in the Xcode project) After you run dart run tool/apply_app_config.dart
primaryColor #0ECE7A "#RRGGBB" or "#AARRGGBB" Your brand colour The next build or flutter run
buyColor #0ECE7A "#RRGGBB" or "#AARRGGBB" Buy, and prices going up The next build or flutter run
sellColor #FF5A5F "#RRGGBB" or "#AARRGGBB" Sell, and prices going down The next build or flutter run
accentColor #1890FF "#RRGGBB" or "#AARRGGBB" Informational accents The next build or flutter run

An identifier made of lowercase letters and digits, such as com.myexchange.app, is valid on both platforms. Using the same one for Android and iOS is the usual choice and the least confusing.

The file is packed into the app when it is built, so every change here needs a build. If the app is already running under flutter run, stop it and run it again — do not rely on a hot reload to pick up an edited app_config.json.

Example

Only the keys this page covers are shown. Keep the rest of your file as it is.

{
  "baseUrl": "https://myexchange.com",
  "wsBaseUrl": "wss://myexchange.com",
  "appName": "MyExchange",
  "androidApplicationId": "com.myexchange.app",
  "iosBundleId": "com.myexchange.app",
  "iosTeamId": "A1B2C3D4E5",
  "primaryColor": "#2F6BFF",
  "buyColor": "#16C784",
  "sellColor": "#EA3943",
  "accentColor": "#F0B90B"
}

What happens at build time

Android: nothing to run

Gradle reads app_config.json every time it builds — flutter run, flutter build apk and flutter build appbundle alike. It sets the applicationId from androidApplicationId and the home-screen name from appName; the manifest's android:label is the placeholder ${appName}, which the build fills in. A change is picked up by the next build. You do not need flutter clean.

Gradle logs the identity it used on every build. flutter build hides Gradle's log unless you add -v, so to see it, run flutter build apk -v (or flutter build appbundle -v) and look for:

App identity from assets/config/app_config.json: name="MyExchange" applicationId=com.myexchange.app

If app_config.json is missing or empty, the build uses the example file, as the app itself does, and the same line ends with (assets/config/app_config.json is missing or empty, so the tracked template was used). dart run tool/apply_app_config.dart --check reports the same name and id without a build and without changing anything.

The build stops, rather than building under the template's identity, when:

  • app_config.json is not valid JSON. The message names the place (at line 4 column 2) and the likely cause. The build's parser is as strict as the app's, so a trailing comma, a doubled comma or a raw line break inside a value is refused here instead of producing an app that could only open on its configuration error screen.
  • androidApplicationId is not a valid Android id. The message says what the id needs and ends "Remove the key to keep com.bicrypto.mobile."
  • appName contains a control character: "The name on the home screen must be a single line of text."

build.gradle.kts still says namespace = "com.bicrypto.mobile", and MainActivity.kt sits in a com/bicrypto/mobile folder. That is the Kotlin package of the app's own code, not its identity on the phone or on Play, and it is deliberately independent of the applicationId. Leave it alone: you never have to rename a Kotlin package to use your own id.

iOS: the name is automatic, the bundle id is one command

The name. The Runner target has a build phase called Apply app_config.json. On every iOS build — flutter run, flutter build ios, flutter build ipa, or an Archive in Xcode — it reads appName and writes it into the built app as CFBundleDisplayName (the name under the icon) and CFBundleName. The source ios/Runner/Info.plist still says BiCrypto; that is the default, and the build phase replaces it only in the built copy. Leave the source file as it is.

To see what that phase will do without running a build, run this from the project root. It needs no Xcode, works on Windows and Linux, and changes nothing:

dart tool/ios_app_identity.dart --dry-run

On a project whose bundle id has been applied (see below), it prints:

[dry run] would run: /usr/bin/plutil -replace CFBundleDisplayName -string "MyExchange" $TARGET_BUILD_DIR/$INFOPLIST_PATH
[dry run] would run: /usr/bin/plutil -replace CFBundleName -string "MyExchange" $TARGET_BUILD_DIR/$INFOPLIST_PATH
[dry run] app_config.json: iOS home-screen name "MyExchange" would be applied; bundle id com.myexchange.app matches the Xcode project

A dry run that finds a problem exits 1 and prints the error a Mac build would stop on, prefixed error: [dry run] an iOS build would stop here:.

The Apply app_config.json phase is covered by the package's tests and the dry run above, but at the time of writing it had not been run inside Xcode. After your first iOS build, check the name under the icon, and check in Xcode's build log that the Apply app_config.json phase ran.

The bundle id. Xcode keeps the bundle id in the project file, and provisioning and code signing read it before any build script runs, so the build cannot set it for you. After you set or change iosBundleId, run this from the project root:

dart run tool/apply_app_config.dart

It writes iosBundleId into ios/Runner.xcodeproj/project.pbxproj for every Runner build configuration (and <your id>.RunnerTests for the test target), changing nothing else in the file. Run it again every time you change the id. Then:

  1. Register the App ID. In your Apple Developer account, under Certificates, Identifiers & Profiles, register an App ID with exactly the same id.
  2. Check signing in Xcode. Open ios/Runner.xcworkspace, select the Runner target, open Signing & Capabilities, choose your Team, and check that the Bundle Identifier shown is your id.
  3. Match Firebase, if you use push. Download GoogleService-Info.plist from a Firebase iOS app registered with the same id. See Push notifications.

If iosBundleId and the Xcode project disagree, the iOS build stops at the Apply app_config.json phase instead of signing and uploading under an id you did not configure. The error names both ways out, because the build cannot know which side is right:

error: iosBundleId in assets/config/app_config.json is "com.myexchange.app", but this build (Release) signs Runner as "com.bicryptto.mobile" (PRODUCT_BUNDLE_IDENTIFIER). If "com.bicryptto.mobile" is your app's id, set "iosBundleId": "com.bicryptto.mobile" in assets/config/app_config.json (or make it "" to skip this check). If "com.myexchange.app" is, run: dart run tool/apply_app_config.dart (it writes iosBundleId into ios/Runner.xcodeproj), or set Xcode -> Runner -> Signing & Capabilities -> Bundle Identifier to "com.myexchange.app".

Leaving iosBundleId empty is allowed. The build then uses whatever id the Xcode project has and does not check it. If you would rather manage the id in Xcode's Signing & Capabilities, leave iosBundleId as "" (as the example ships it) or put the same id in it.

The tool refuses three cases where "make the project match the config" would be the wrong fix, writes nothing, and says what to do instead:

  • The template's id over your own. If iosBundleId is the template's (com.bicrypto… or com.bicryptto…) and the Xcode project already signs as an id of yours, it stops rather than move your app back to the template's identity. --allow-template-id overrides this, for a deliberate reset.
  • Different ids per build configuration. If the Runner target uses, say, com.myexchange.app for Release and com.myexchange.app.dev for Debug, it will not merge them into one. Leave iosBundleId empty and manage those ids in Xcode.
  • An id that is not a literal. A configuration that takes its id from a build variable or an .xcconfig file is left alone; set the id where it is defined.

If you added an app extension (a Notification Service extension, a widget), its id must start with the app's. The tool does not change an extension's id, but it warns when one still starts with the old id and tells you what to set it to in Xcode.

The Team. Set iosTeamId to your Team ID (developer.apple.com → Account → Membership details → Team ID) and dart run tool/apply_app_config.dart writes it into every Runner build configuration of the Xcode project as DEVELOPMENT_TEAM, so the project stops depending on whoever last chose a Team in Xcode. The template carries no Team of its own. Left out or "", nothing is written and the project keeps whatever Team it has; choose yours in Signing & Capabilities as in step 2 above.

A value that is not 10 upper-case letters and digits is refused, and nothing is written. The tool warns if the value is 52K7QMD7KR: that is the Team the template's Xcode project carried up to 5.3.9, and it signs for nobody else. --check exits 1 when the project's Team differs from a set iosTeamId.

Check before you build: apply_app_config.dart

Run from the project root, after flutter pub get.

Command What it does Writes
dart run tool/apply_app_config.dart Validates the backend addresses, the name, both identifiers, the Team and the colours, writes iosBundleId and iosTeamId into the Xcode project, and prints what each platform will use ios/Runner.xcodeproj/project.pbxproj
dart run tool/apply_app_config.dart --check The same report, changing nothing. Exits 1 when the config is invalid or the Xcode project's bundle id or Team differs from iosBundleId or iosTeamId — the question to ask right before a build, or in CI Nothing
dart run tool/apply_app_config.dart --check --release As --check, and an http:// baseUrl or ws:// wsBaseUrl is an error rather than a warning, as it is for a release build Nothing
dart run tool/apply_app_config.dart --icons Also regenerates the launcher icons from assets/icons/app_icon.png (see The icon) The Android and iOS icon files
dart tool/ios_app_identity.dart --dry-run Previews the iOS build phase on any machine Nothing
dart run tool/release_preflight.dart --explain Refuses a build still carrying the template's identity; the last step before a release build Nothing

It reports every problem in the file at once, not one per run. The first run on the example file above, with your 1024×1024 image in place but before the launcher icons have been generated from it, looks like this:

Applied assets/config/app_config.json

Name
  in the app             "MyExchange"
  Android home screen    "MyExchange"  (android/app/build.gradle.kts reads appName on every build)
  iOS home screen        "MyExchange"  (the "Apply app_config.json" build phase writes it into every build)

Identifiers
  Android applicationId  com.myexchange.app  (androidApplicationId in app_config.json; Gradle reads it on every build, nothing to write)
  iOS bundle id          com.myexchange.app  (iosBundleId; written into ios/Runner.xcodeproj/project.pbxproj: 3 Runner and 3 RunnerTests settings, was com.bicryptto.mobile)
  iOS signing team       A1B2C3D4E5  (iosTeamId; written into ios/Runner.xcodeproj/project.pbxproj: 3 Runner configurations, was (none))

Colours (read by the app at start-up)
  primaryColor           #2F6BFF    custom
  buyColor               #16C784    custom
  sellColor              #EA3943    custom
  accentColor            #F0B90B    custom

Icon
  assets/icons/app_icon.png: 1024x1024 PNG; the launcher icons are still the template's

warning: the launcher icons that ship are still the template's. Run: dart run tool/apply_app_config.dart --icons

Next: dart run tool/release_preflight.dart

Run it a second time and the bundle id line says ios/Runner.xcodeproj/project.pbxproj already has it, nothing to change, and the Team line says already in ios/Runner.xcodeproj/project.pbxproj. Without iosTeamId, the Team line reads none (iosTeamId is not set; choose your team in Xcode -> Runner -> Signing & Capabilities, or set iosTeamId).

An invalid value is refused with the fix, and nothing is written:

error: assets/config/app_config.json has 1 problem:
  - "primaryColor" is "blue", which is not a colour. Write it as "#RRGGBB" (for example "#0ECE7A") or "#AARRGGBB" (alpha first), or remove the line to use the default
Nothing was changed. Fix it and run this again.

It also warns, without failing, when the name or an id is still the template's; when the name is longer than 12 characters (about what the iOS home screen shows under an icon before cutting it with an ellipsis) or 30 (Google Play's limit for an app title); and when the icon is not square or is smaller than 1024×1024.

Colours

Four colours brand the app, in both the dark and the light theme.

Key What it paints
primaryColor Your brand colour: primary buttons, selected tabs, links, and the outline of the field being typed in — whatever the app treats as its main colour.
buyColor Buy buttons and labels, prices going up, positive changes.
sellColor Sell buttons and labels, prices going down, negative changes.
accentColor A second accent, for informational highlights on some screens.
(not configurable) Success stays green (#0ECE7A) and errors stay red (#FF5A5F), whatever your brand colours are, apart from the screens named below.

Success and error marks do not follow your trading colours, so you can match your market's conventions without breaking them. If rising prices are red in your market, set buyColor to a red and sellColor to a green: buy and sell, and prices going up and down, take the swapped colours, while a "saved" tick stays green and an error stays red.

Text on primary-coloured buttons is white, and switches to dark text on a very light primary (a yellow, a pastel) so it stays readable. A mid-tone primary keeps white text.

The exception in this release is the Transfer screens and the wallet's Deposit screens. They still colour their own success and error marks with buyColor and sellColor, and some of their buttons and selected items keep white text on primaryColor. If your trading colours are far from green and red, or your primary colour is very light, check those screens before you publish.

Backgrounds, text greys, borders and the warning orange are not brand colours and do not change.

Format. A # and six hex digits (#2F6BFF), or eight where the first two are the opacity (#FF2F6BFF; FF is fully solid), in either case. Anything else — "blue", "#12345", "0ECE7A" without the # — is a configuration error, never a silent fallback to green: the app opens on its Configuration Error screen, and the box on it names the key:

Exception: Failed to load app configuration. assets/config/app_config.json sets "primaryColor" to "blue", which is not a colour. Write it as "#RRGGBB" (for example "#0ECE7A") or "#AARRGGBB" (alpha first), or remove the line to use the default. Then stop the app and build again.

dart run tool/apply_app_config.dart --check refuses the same values, so run it before you build rather than finding out on a phone.

Traders read colour before they read text. Keep buyColor recognisably "up" and sellColor recognisably "down" (green/red, or blue/orange), keep them clearly different from each other, and look at the result in both themes.

The icon

The icon is a file, not a key.

  1. Put your image in place. A square PNG of at least 1024×1024 at assets/icons/app_icon.png. The release preflight refuses a smaller source, because the generator upscales it without a word and the blur ends up on your store listing. Use a solid background: the App Store does not accept an icon with transparency, and the iOS icons are generated with the alpha channel removed, so transparent areas do not come out as you drew them.

  2. Generate the platform icons.

    dart run tool/apply_app_config.dart --icons

    This runs flutter_launcher_icons, which writes the Android icons (android/app/src/main/res/mipmap-*) and the iOS icons (ios/Runner/Assets.xcassets/AppIcon.appiconset) from your image.

  3. Build and reinstall. If the old icon still shows, uninstall the app and install the new build.

Run step 2 again every time you replace the image. The generated files are what ships, not app_icon.png itself, so a new image without this step still installs with the old icon — and the release preflight says so ([launcher-icon] the Android launcher icon is still the one this template ships).

The assets/icons/app_icon.png the package ships is a 281-byte text note, not an image, and --icons refuses it ("cannot generate the launcher icons: assets/icons/app_icon.png is the shipped placeholder, not a PNG image"). The generated icons already in android/ and ios/, though, are the template's own — so an app built without step 2 installs with the template's icon. That is why the preflight checks those generated files and not only your source image.

On Android, step 2 generates a standard launcher icon, not an adaptive one. The android_adaptive_icon: block in the shipped pubspec.yaml is not a setting flutter_launcher_icons reads, so editing it changes nothing. If you want an adaptive icon, add adaptive_icon_foreground (an image path) and adaptive_icon_background (an image path, or a colour such as "#FFFFFF") under flutter_launcher_icons: in pubspec.yaml before step 2.

What changing each one costs

The name. Change it whenever you like. It is the same app: updates install over existing installs, and users see the new name once they update. The name on your store listing is set separately, in Google Play Console and App Store Connect.

androidApplicationId. This is the app, as far as Android and Google Play are concerned. A different value makes a different app:

  • It needs a new Play listing; Play will not accept it as an update to your existing one.
  • Existing installs will not update to it. Both apps can sit on the same phone.
  • android/app/google-services.json (Firebase, for push) must come from a Firebase Android app registered with the same id. The build refuses a file that has no entry for it, with "No matching client found for package name".
  • Restrict your Google sign-in and other client credentials to the new id in their providers' consoles.

iosBundleId. The same on Apple's side. It must match the App ID you registered and the provisioning profile you sign with. A different value after release is a different App Store app, and GoogleService-Info.plist must be for the same id.

Upgrading from an older release

Releases up to and including 5.3.9 kept the name in AndroidManifest.xml and Info.plist, the Android id as a literal in android/app/build.gradle.kts, the iOS id in the Xcode project, and the colours in lib/core/theme/app_themes.dart. If you customised any of those by hand, carry the values into app_config.json before you build the update:

  1. Your app name goes into appName.
  2. Your Android id — the applicationId = "…" line in your old android/app/build.gradle.kts — goes into androidApplicationId. Do not skip this. Without it the update builds as com.bicrypto.mobile, a different app from the one your users have installed, and Play will not accept it for your listing.
  3. Your iOS id (Xcode: Runner target, Signing & Capabilities, Bundle Identifier) goes into iosBundleId. Then run dart run tool/apply_app_config.dart.
  4. Colours you changed in app_themes.dart go into primaryColor, buyColor, sellColor and accentColor.
  5. Take this release's native files together: android/app/build.gradle.kts, android/app/src/main/AndroidManifest.xml and ios/Runner.xcodeproj. The new Gradle file reads app_config.json, the new manifest's label is ${appName}, and the new Xcode project carries the Apply app_config.json phase. It carries no Team. After taking it, set iosTeamId (or choose your Team in Signing & Capabilities) and run dart run tool/apply_app_config.dart again.
  6. Regenerate the icons if you replaced the android/ and ios/ folders, because your generated icons went with them: dart run tool/apply_app_config.dart --icons.
  7. Run the preflight: dart run tool/release_preflight.dart --explain.

The files in step 5 only work as a set, and the preflight names each half-way state it finds:

What you kept What happens What the preflight says
An older build.gradle.kts with the new manifest Every Android build fails: "Manifest merger failed … no value for <appName> is provided" [android-label-placeholder-missing]
An older manifest with a typed-in android:label The Android home screen shows that label, whatever appName says [android-label-hardcoded] when the two differ; apply_app_config.dart --check warns either way
An older Xcode project without the phase The iOS home screen shows whatever Info.plist says, and nothing checks iosBundleId [ios-name-phase-missing]
A typed-in applicationId in build.gradle.kts that differs from androidApplicationId The Gradle literal wins [android-id-hardcoded]
The new build.gradle.kts, without moving your id into androidApplicationId The build uses com.bicrypto.mobile [bundle-id] Android applicationId is still "com.bicrypto.mobile"

The example file, and the app_config.json the package ships, contain "iosBundleId": "". Empty means the Xcode project keeps the id it has and the build does not check it, so taking an update never moves an iOS app you already publish. Put your id there when you want the config to be the place it is set.

Everything else an update involves — the files it does not contain, the build number, re-applying your own code changes — is on Updating your app.

Last step: the release preflight

Run this from the project root before every release build:

dart run tool/release_preflight.dart --explain

It exits 1 while the build still carries the template's name, identifiers, colours, icon or placeholder backend, printing each item with its fix. It also refuses an Xcode project with no Team for Release, or with the old 52K7QMD7KR ([ios-team]), and one whose Team differs from iosTeamId ([ios-team-mismatch]). If you publish on Android only, run it with --android-only: that skips every iOS check and keeps the Android ones. A differentiated build prints:

release preflight: this build has been differentiated from the template

It also checks things that are not branding but cost a release when missed — the release signing key, the Android target API level, and the iOS push entitlement when Firebase is configured. What it checks, and why Apple's rule 4.2.6 makes this a shipping requirement rather than a preference, is on Differentiation.

What this does not change

  • Your store listings. The name, icon and screenshots people see in Google Play and the App Store are set in Play Console and App Store Connect.
  • Your website's branding. The logos and colours you set for the web platform (Design, menus, footer and branding) do not reach the app. It shows no logo or colour from your server's settings; its name, colours and icon come only from the build.
  • The launch screen. Both platforms keep Flutter's plain default launch screen, which carries no logo of its own. A branded one is a native change (android/app/src/main/res/drawable*/launch_background.xml, and the LaunchImage set in ios/Runner/Assets.xcassets) that no key covers.
  • The app-switcher cover. When the app loses focus — for example when the recent-apps view opens — it covers itself with a dark panel and your launcher icon, so balances are not left on show there. The panel's colour is fixed in native code (MainActivity.kt, AppDelegate.swift); only the icon on it is yours.
  • Where the name appears inside the app. appName is the wordmark on the loading screen and on the sign-in, sign-up and password-reset screens, and it is used in text such as "Join MyExchange to start trading" on sign-up and "MyExchange Support" in the live chat header. You do not choose where.

When the name or icon did not change

The home screen still shows the old name

Check that you edited assets/config/app_config.json, not the example. Build and install again: the name is applied at build time, so an app built before your change keeps the old name. Some Android launchers remember an old label — if the name inside the app is right but the home screen is not, uninstall and reinstall. On iOS, check in Xcode's build log that the Apply app_config.json phase ran. If you carried native files over from an older release, see Upgrading from an older release.

The iOS build stops at "Apply app_config.json"

iosBundleId and the Xcode project's bundle id differ. If the config is right, run dart run tool/apply_app_config.dart. If the project is right (you changed the id in Xcode), put its id into iosBundleId, or make iosBundleId empty. Then check Signing & Capabilities.

The Android build fails with "no value for <appName> is provided"

Your android/app/build.gradle.kts is from an older release and your AndroidManifest.xml is from this one. Move the applicationId from your old Gradle file into androidApplicationId, then take this release's build.gradle.kts.

The Android build fails with "No matching client found for package name"

android/app/google-services.json was downloaded for a different id. In the Firebase console, add an Android app with your androidApplicationId, download its google-services.json into android/app/, and build again.

The app opens on "Configuration Error" naming a colour

That key is not #RRGGBB or #AARRGGBB. Fix the value, or empty it to use the default, and build again.

The icon did not change

Run dart run tool/apply_app_config.dart --icons after replacing assets/icons/app_icon.png, then uninstall the app and install the new build. If it still shows the old icon, run flutter clean and flutter pub get, then build again.