fix(ui): preserve '-rc.8' in About dialog despite iOS version sanitisation (rc.8+65)

Two related fixes for the version-display work landed at 97a6ba6:

PROBLEM 1: build counter stuck at +59 on the device.
The user reported the About dialog showed v1.0.0-rc.8+59 even
though pubspec.yaml has been bumping (60 -> 61 -> 62 -> 63 -> 64).
Root cause: Xcode caches ios/Flutter/Generated.xcconfig (which
holds FLUTTER_BUILD_NUMBER) and doesn't regenerate it on Cmd+R
unless inputs change. 'flutter pub get' also doesn't rewrite it.
Only 'flutter build ios' or deleting the file forces regen.

Fix (operational, not code-side): the Generated.xcconfig file on
the Mac was deleted + regenerated and is now at
FLUTTER_BUILD_NUMBER=64, so the next Xcode Run picks up the
correct value. Going forward, if the build counter ever lags
again the workaround is:

    rm ios/Flutter/Generated.xcconfig && flutter pub get

before running from Xcode. We may add this to a build-doc note
or a Makefile target during the rc.8 wrap-up.

PROBLEM 2: 'v1.0.0-rc.8' was being displayed as 'v1.0.0.8'.
CFBundleShortVersionString on iOS rejects non-numeric
characters, so Flutter strips the '-rc.8' suffix to '.8' when
populating Info.plist. package_info_plus.version reflects that
mangled value. CFBundleVersion (the build counter) is passed
through intact, so the issue affects only the semver half.

Fix: split _kAppVersion resolution. Hardcode the semver baseline
as _kSemverBaseline = 'v1.0.0-rc.8' (kept in sync with the git
tag + pubspec semver portion; bumped once per release-candidate
cycle, not per test build). Use package_info_plus for the
'+<buildNumber>' suffix only, where CFBundleVersion survives the
sanitiser unchanged. Final display becomes
'v1.0.0-rc.8+<n>' (e.g. 'v1.0.0-rc.8+65').

flutter analyze: clean.

Build counter 64 -> 65. After Xcode Clean Build Folder + Run the
About dialog should now read 'v1.0.0-rc.8+65' on the iPhone.
This commit is contained in:
EdisonJwa
2026-05-17 01:18:06 +08:00
parent e7c3ffa6d2
commit ecf9370db1
2 changed files with 34 additions and 24 deletions
+33 -23
View File
@@ -37,21 +37,29 @@ bool get _isTouchOnlyPttHost {
}
/// Public version string shown in the About dialog. Resolved at
/// app init from `package_info_plus`, which reads the platform-
/// canonical version (iOS `CFBundleShortVersionString` + `CFBundleVersion`,
/// Android `versionName` + `versionCode`). Flutter populates both
/// from the SAME `pubspec.yaml` `version:` field, so whatever the
/// user sees in the About dialog is guaranteed to match the
/// commit that produced the build they're running. The "+build"
/// suffix is the post-`+` portion of the pubspec version and
/// distinguishes successive test builds during the iOS audio
/// rollout (every test commit bumps the build number).
/// app init by combining a hardcoded semver baseline (kept in sync
/// with the git tag and pubspec.yaml's `version:` field) with the
/// platform-canonical build counter from `package_info_plus`.
///
/// Initialised before `runApp` in `main()`. Falls back to the
/// hardcoded baseline if `PackageInfo.fromPlatform()` fails
/// (extremely unlikely — the platform channel call is a simple
/// constant lookup).
String _kAppVersion = 'v1.0.0-rc.8';
/// Why hardcode the semver instead of reading the whole string
/// from `package_info_plus`: iOS rejects non-numeric characters
/// in `CFBundleShortVersionString` and Flutter therefore strips
/// `-rc.8` to `.8` when populating the Info.plist field. The
/// resulting `1.0.0.8` is technically valid on the App Store but
/// useless to humans tracking pre-release builds.
/// `package_info_plus.version` reflects that mangled value. The
/// build counter (`CFBundleVersion` / Android `versionCode`) does
/// pass through unmodified, so we use platform info for the
/// `+<build>` suffix only and pair it with the human-readable
/// semver baseline that this codebase already maintains as the
/// canonical release identity.
///
/// Bump `_kSemverBaseline` whenever the semver portion of
/// pubspec.yaml advances (e.g. rc.8 -> rc.9 -> 1.0.0). The
/// build-counter suffix changes automatically on every pubspec
/// `+<n>` bump because Flutter writes it into Info.plist.
const String _kSemverBaseline = 'v1.0.0-rc.8';
String _kAppVersion = _kSemverBaseline;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -62,20 +70,22 @@ Future<void> main() async {
runApp(const ChanoraApp());
}
/// Populate `_kAppVersion` from the platform manifest. Format is
/// `v<version>+<build>` (e.g. `v1.0.0-rc.8+60`). The `+<build>` is
/// the iOS `CFBundleVersion` / Android `versionCode`, kept in
/// sync with `pubspec.yaml`'s `version: <semver>+<build>`.
/// Populate `_kAppVersion` by suffixing the platform-canonical
/// build number to `_kSemverBaseline`. Format:
/// `v1.0.0-rc.8+<build>` (e.g. `v1.0.0-rc.8+64`). The build
/// number is iOS `CFBundleVersion` / Android `versionCode`,
/// kept in sync with pubspec.yaml's `version: <semver>+<build>`.
Future<void> _resolveAppVersion() async {
try {
final info = await PackageInfo.fromPlatform();
// info.version = "1.0.0-rc.8" (CFBundleShortVersionString)
// info.buildNumber = "60" (CFBundleVersion)
// info.buildNumber = "64" (CFBundleVersion on iOS; survives
// the iOS version-string sanitiser that mangles
// CFBundleShortVersionString).
final build = info.buildNumber.isEmpty ? '' : '+${info.buildNumber}';
_kAppVersion = 'v${info.version}$build';
_kAppVersion = '$_kSemverBaseline$build';
} catch (_) {
// Keep the hardcoded fallback. Already-correct for the
// semver portion; only the build counter is lost.
// Keep the hardcoded baseline. The build counter is lost
// but the semver stays correct.
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0-rc.8+64
version: 1.0.0-rc.8+65
environment:
sdk: ^3.11.5