Updating your app to a new release
Take a new source release of the app without losing your work. Which files are yours and must be carried across, what the new zip overwrites, why your branding now survives an update, and the build number, checks and resubmission that follow.
A new release of the app is a new zip: the complete source tree, not a patch and not a repository you can pull from. It has no git history. Your app, on the other hand, lives in a handful of files the zip does not contain, or contains only as template values. Updating means starting from the new tree, carrying your files across, re-applying any code changes of your own, and building with a build number the stores have not seen.
Nothing changes on your customers' phones until they install your update from the store. Your server is updated separately, on its own schedule.
Before you start
- The project folder you built your live app from. Leave it untouched until the update is published; it is your only copy of your own changes.
- The new release zip.
- Your upload keystore and its passwords.
- The highest build number you have uploaded to Google Play and to App Store Connect.
- A list of the code changes you made yourself, if any.
Why an update is a copy, not a pull
The zip carries no .git folder, so there is nothing to git pull. If an
older guide tells you to update with git pull origin main, that step does not
apply to a downloaded release.
Nothing in the package compares your changes with a new release either. The
tools in tool/ check your configuration and the build, not your code. Any
change you made to files under lib/, android/ or ios/ has to be made
again, by hand, on the new tree.
If you change the code, keep the project in a git repository of your own:
commit each release exactly as it arrives, then your changes on top of it. When
the next release comes, commit it the same way and let git show you what moved
under your changes. Ignore assets/config/app_config.json,
android/key.properties, your keystore, android/app/google-services.json and
ios/Runner/GoogleService-Info.plist before the first commit. The zip ships no
top-level .gitignore that would do it for you.
What is yours
These files hold your app. A new release either does not contain them, or contains a template version that must not replace yours.
| File | What it holds | In the new zip |
|---|---|---|
assets/config/app_config.json |
Everything that makes the build yours: baseUrl, wsBaseUrl, appName, androidApplicationId, iosBundleId, iosTeamId, the four colours, your keys |
Yes, but as a copy of the example, pointing at https://your-backend-url.com |
assets/icons/app_icon.png |
The source of your launcher icons | Yes, as the template's placeholder, which is not an image |
android/key.properties |
The path and passwords of your upload key | No |
Your upload keystore (.jks) |
The key every Play update must be signed with | No. Keep it outside the project folder |
android/app/google-services.json |
Android push, if you use it | No |
ios/Runner/GoogleService-Info.plist |
iOS push, if you use it | No |
| Your own code changes | Whatever you changed under lib/, android/ or ios/ |
No |
Three more things live inside files the new release replaces, so they are lost when you take the new file rather than because the zip lacks them:
- The iOS Push Notifications capability. It lives in
ios/Runner.xcodeproj(with theRunner.entitlementsfile Xcode creates). A new Xcode project does not have it. If you use push on iOS, add it again in Xcode after every update. See Build for iOS. - Entries you added to
ios/Runner/Info.plist, for example for Google sign-in on iOS. The newInfo.plistdoes not have them. See Sign-in methods. - Your version line.
version:inpubspec.yamlarrives as the release's own number. See The build number.
The app_config.json inside every release is a copy of the example. A build
made from it, or from a tree where the release's assets/ folder was copied
over yours, talks to the placeholder host. The file exists, so the app shows no
error: every screen simply fails as if your server were down. Copy your
file into the new tree. The release preflight reports the placeholder as
backend-url.
Why your branding survives an update now
Your app's name, both identifiers, your Apple team and your colours are all
set in assets/config/app_config.json, and the icon comes from
assets/icons/app_icon.png. Each reaches the build from there:
- Android.
android/app/build.gradle.ktsreadsappNameandandroidApplicationIdfrom the config on every build. - iOS. The Apply app_config.json build phase writes
appNameinto every build.dart run tool/apply_app_config.dartwritesiosBundleIdandiosTeamIdinto the Xcode project, because those two are build settings that no build phase can change. - Colours. The app reads them from the config when it starts.
- Icon.
dart run tool/apply_app_config.dart --iconsturns your PNG into every launcher icon.
So you take the release's native files exactly as they come and carry two
files of your own. Nothing of yours has to be typed into a vendor file again.
The release's Xcode project carries no Apple team at all, so your team comes
back either from iosTeamId in your config or from choosing it in Xcode.
Releases up to and including 5.3.9 worked differently: the name was typed into
AndroidManifest.xml and Info.plist, the Android ID into
build.gradle.kts, the iOS ID and team into the Xcode project, and the colours
into lib/core/theme/app_themes.dart. If your live app comes from one of those
and you customised any of them by hand, move each value into app_config.json
before you build the update. The steps are in
Branding: upgrading from an older release.
Google Play accepts an update only under the application ID it already has,
and the App Store only under the bundle ID it already has. With no
androidApplicationId in your config, the build uses the template's
com.bicrypto.mobile, which is a different app from yours. Before you build,
read the Identifiers lines that dart run tool/apply_app_config.dart
prints and check both are the IDs you already publish under.
Steps
-
Unzip the release into a new folder, next to your current project. Do not unzip it over your project, and do not copy its
assets/folder over yours. -
Carry your configuration across. Copy
assets/config/app_config.jsonfrom your current project into the new one, replacing the release's copy. Coming from 5.3.9 or earlier with hand-edited native files? Move those values into the config first, as described above. Keys an older config carries that this release no longer reads are ignored, and you can delete them; see Keys the app ignores. -
Carry your icon across. Copy your
assets/icons/app_icon.pnginto the new tree. -
Carry your signing across. Copy
android/key.properties. ItsstoreFileshould be an absolute path to your keystore. A relative path is resolved from the new project'sandroid/app/, where your keystore is not. -
Carry your Firebase files across, if you use push:
android/app/google-services.jsonandios/Runner/GoogleService-Info.plist. -
Re-apply your own code changes, if you made any.
-
Fetch the dependencies. From the project root:
flutter pub getOn a Mac, also run
cd ios && pod install. -
Apply the config and regenerate the icons.
dart run tool/apply_app_config.dart --iconsThis writes
iosBundleIdandiosTeamId, when your config sets them, into the new Xcode project, regenerates the Android and iOS launcher icons from your PNG, and prints the name, identifiers, team and colours each platform will use. Read them. WithoutiosTeamId, choose your team in Xcode under Signing & Capabilities instead. -
Restore iOS push, if you use it: in Xcode, add
GoogleService-Info.plistto the Runner target and add the Push Notifications capability again. See Build for iOS. -
Set a new build number. See below.
-
Check the release.
dart run tool/apply_app_config.dart --check --release dart run tool/release_preflight.dart --explainFix every item they report.
--releasemakes anhttp://orws://server address an error, because a release build refuses one on its first screen. The preflight also names native files still left from an older release, and an Xcode project with no team; if you publish on Android only, add--android-onlyto skip its iOS checks. The full list of its checks is in the build and tools reference. -
Run it once, then build and upload. A debug run against your server shows your name, your colours and live prices. Then build as usual: Build for Android, Build for iOS.
The build number
Both stores refuse a build number they have already received. The number is
the part after + in the version: line of pubspec.yaml, and a new release
arrives with its own line, for example version: 5.3.9+10. If your own
uploads have already gone past that number, the stores refuse the update until
you raise it.
Either edit the line, or leave it and pass the numbers when you build:
flutter build appbundle --release --build-name=5.4.0 --build-number=23The version name is also what customers see in the app, under the gear icon on
Home → Open-source Licenses, as for example 5.4.0 (23), and what they
quote to support. If you use your own version names, keep a note of which
release each one was built from.
Your server is updated separately
Updating the app does not update your server, and updating your server does not update the app on anyone's phone. Customers keep the build they have until they install yours from the store, and nothing on the server can make them. The Minimum iOS App Version and Minimum Android App Version settings in the admin are not read by the app.
Some app fixes and features need the server half as well. The
Also needs column in
Problems fixed in a later version
says which. For example, the Market News tile on Home appears only when
your core reports the news module, which it does from 6.8.2.
What it deliberately does not do
Being clear about this now saves a support ticket later.
- It does not update over the air. The configuration is packed into each build, and there is no remote configuration. A new backend address, a new colour or a new name reaches customers only as a store update.
- It does not merge your code. No tool in the package compares your changes with a new release. Keep your own repository if you change code.
- It does not move your iOS bundle ID on its own. The example config ships
"iosBundleId": "", which means "keep the Xcode project's ID and do not check it". Set your ID there when you want the config to be the single place it lives; then an iOS build whose project disagrees stops at the Apply app_config.json phase and says how to fix it.