feat(ui): show build number in About dialog (v1.0.0-rc.8+<build>)

Add package_info_plus 8.3.1 (Flutter Community Plus, BSD-3, ~3M
downloads) and resolve the displayed version string from the
platform manifest at app init. The string shown in the About
dialog is now formatted as 'v<version>+<build>' (e.g.
'v1.0.0-rc.8+60') where both halves come from the SAME
pubspec.yaml 'version:' field that Flutter uses to populate iOS's
CFBundleShortVersionString + CFBundleVersion and Android's
versionName + versionCode.

Motivation: during the iOS VoiceProcessingIO audio rollout the
user is rebuilding repeatedly from Xcode. Without a build-number
suffix there is no way to confirm from inside the app which
commit produced the build under test — they all read
'v1.0.0-rc.8'. Every test commit going forward bumps the +<build>
field in pubspec.yaml so the About dialog uniquely identifies the
build.

Implementation:
* pubspec.yaml: version 1.0.0-rc.8+59 -> +60 (this commit is a
  test build). Add package_info_plus: ^8.0.0.
* main.dart: _kAppVersion changes from 'const String' to
  'String' (resolved at runtime). Populated in main() before
  runApp() via new _resolveAppVersion() helper that calls
  PackageInfo.fromPlatform() and formats 'v<info.version>+<info.buildNumber>'.
  Falls back to the hardcoded 'v1.0.0-rc.8' baseline if the
  platform call fails (extremely unlikely; the platform channel
  is a constant lookup).

The consumer site at the About dialog (l10n.aboutVersion(_kAppVersion))
is unchanged — the variable still resolves to a String. flutter
analyze: clean.
This commit is contained in:
EdisonJwa
2026-05-17 00:49:05 +08:00
parent 3b1724e970
commit 97a6ba6b55
3 changed files with 79 additions and 6 deletions
+35 -5
View File
@@ -16,6 +16,7 @@ import 'dart:io' show Platform;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'l10n/generated/app_localizations.dart'; import 'l10n/generated/app_localizations.dart';
@@ -35,20 +36,49 @@ bool get _isTouchOnlyPttHost {
return Platform.isIOS || Platform.isAndroid; return Platform.isIOS || Platform.isAndroid;
} }
/// Public version string shown in the About dialog. Aligned with /// Public version string shown in the About dialog. Resolved at
/// `pubspec.yaml` and the git tag for the MVP release candidate. /// app init from `package_info_plus`, which reads the platform-
/// Bumped to rc.8 for the post-rc.7 v1 audio + PTT lifecycle work /// canonical version (iOS `CFBundleShortVersionString` + `CFBundleVersion`,
/// (SDD-094..097, DEC-029..031). /// Android `versionName` + `versionCode`). Flutter populates both
const String _kAppVersion = 'v1.0.0-rc.8'; /// 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).
///
/// 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';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await RustLib.init(); await RustLib.init();
await _resolveAppVersion();
unawaited(_wireStorage()); unawaited(_wireStorage());
unawaited(_wireConnectivity()); unawaited(_wireConnectivity());
runApp(const ChanoraApp()); 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>`.
Future<void> _resolveAppVersion() async {
try {
final info = await PackageInfo.fromPlatform();
// info.version = "1.0.0-rc.8" (CFBundleShortVersionString)
// info.buildNumber = "60" (CFBundleVersion)
final build = info.buildNumber.isEmpty ? '' : '+${info.buildNumber}';
_kAppVersion = 'v${info.version}$build';
} catch (_) {
// Keep the hardcoded fallback. Already-correct for the
// semver portion; only the build counter is lost.
}
}
Future<void> _wireStorage() async { Future<void> _wireStorage() async {
try { try {
final dir = await getApplicationSupportDirectory(); final dir = await getApplicationSupportDirectory();
+32
View File
@@ -309,6 +309,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.0.3"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@@ -469,6 +477,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
url: "https://pub.dev"
source: hosted
version: "8.3.1"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -730,6 +754,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "3.0.3"
win32:
dependency: transitive
description:
name: win32
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
url: "https://pub.dev"
source: hosted
version: "5.15.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
+12 -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 # 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 # 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. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.0-rc.8+59 version: 1.0.0-rc.8+60
environment: environment:
sdk: ^3.11.5 sdk: ^3.11.5
@@ -63,6 +63,17 @@ dependencies:
# wrong UI: only lists AirPlay output destinations, not the # wrong UI: only lists AirPlay output destinations, not the
# speaker/receiver/Bluetooth choices we actually want. # speaker/receiver/Bluetooth choices we actually want.
audio_session: ^0.2.3 audio_session: ^0.2.3
# package_info_plus 8.x (Flutter Community Plus, BSD-3, ~3M
# downloads) exposes the platform-canonical app version + build
# number at runtime so the About dialog (and any future
# diagnostic export) can display "v1.0.0-rc.8+60" sourced from
# the SAME pubspec.yaml field that drives CFBundleShortVersionString
# + CFBundleVersion on iOS and versionName + versionCode on
# Android. Single source of truth means the version on the
# iPhone screen always matches the commit that built it; no
# more "am I testing the right build?" question during the
# iOS audio test cycle.
package_info_plus: ^8.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: