Fix iOS upgrade popup reappearing after update due to build metadata in version string (fixes: #480)#552
Conversation
…ersion strings PackageInfo.version on iOS can return a version with build metadata (e.g. 1.2.3+45) while the App Store always returns a clean semantic version (1.2.3), causing a false update-available comparison. parseVersion() now strips the +build suffix before parsing, and all direct Version.parse() call sites on user/store-supplied strings are routed through the same normalization.
|
@mem-5514-tahara Thanks for sharing this PR with the community. I would like to see more evidence in support of the root cause. |
|
Hi @larryaasen, Thank you for pushing back and asking for more evidence. You were absolutely right to do so! I wrote a test script to rigorously verify the behavior of Here is the script I ran: import 'package:version/version.dart';
void main() {
// 1. Testing Apple-style non-SemVer strings
for (final v in ['1.0', '1.2.3.4', '1.2.3+45', '1.2.3', '5.6']) {
try {
final parsed = Version.parse(v);
print('Version.parse("$v") => $parsed');
} catch (e) {
print('Version.parse("$v") => THROWS: $e');
}
}
// 2. Testing comparison with build metadata
final installed = Version.parse('1.2.3+45');
final store = Version.parse('1.2.3');
print('installed == store : ${installed == store}');
print('installed > store : ${installed > store}'); // Originally thought this was true
}Output: It turns out package:version is incredibly robust. It safely auto-pads 2-digit versions, truncates 4-digit versions, and correctly ignores build metadata during comparisons. There are no FormatExceptions or comparison flaws. Since the parsing and comparison logic is rock-solid, this PR does not actually fix a bug. Looking closely at Issue #480, since no one has provided the debugLogging output you requested, I strongly suspect this is not an upgrader bug at all. It is highly likely a Configuration Desync on the developers' end (e.g., they hardcoded CFBundleShortVersionString in Xcode, breaking the sync with pubspec.yaml). Their app is genuinely reporting an older version to upgrader than what is on the App Store. I will close this PR for now to avoid adding unnecessary code. If a user in #480 finally provides actual debug logs that point to a real library issue, I'd be happy to help investigate again. Thanks for your time and guidance! |
@larryaasen, thanks for maintaining this package! Here's a fix for the false-positive upgrade prompt reported in #480.
Fixes #480
Problem
On iOS, the upgrade popup reappears even after the user has already updated to the latest version available in the App Store.
Root cause: Flutter's
PackageInfo.versioncan return a version string that includes build metadata (e.g.1.2.3+45), while the iTunes Search API always returns a clean semantic version (1.2.3). BecauseVersion.parse('1.2.3+45')produces a value that compares as greater thanVersion.parse('1.2.3'), the library incorrectly treats the installed version as newer — or fails to recognise that the versions are equal — resulting in a false "update available" state.Solution
Normalize version strings by stripping the
+<build>suffix before parsing, so1.2.3+45and1.2.3are treated as semantically equal.The fix is applied in
Upgrader.parseVersion()— the central parsing utility — so all call sites that already route through it benefit automatically. DirectVersion.parse()call sites inupgrader.darthave been migrated to useparseVersion(). Call sites inupgrade_store_controller.dartandappcast.dart(which cannot importUpgraderwithout a circular dependency) apply the sameversion.split('+').firstnormalization inline.A debug log line is emitted whenever normalization occurs:
Changes
lib/src/upgrader.dart— updatedparseVersion()to strip build metadata; migrated all directVersion.parse()call siteslib/src/upgrade_store_controller.dart— normalized store-supplied version strings inUpgraderAppStore,UpgraderPlayStore, andUpgraderAppcastStorelib/src/appcast.dart— normalized version strings used in best-item selection and host-support checksTests
Added 4 tests in
test/upgrader_test.dart:parseVersion('1.2.3+45')Version.parse('1.2.3'), not greaterparseVersion('1.2.3')1.2.3+45, store1.2.3isUpdateAvailable()→false1.2.2+99, store1.2.3isUpdateAvailable()→trueFull test suite passes:
flutter test --coverage.Notes
package:versionlibrary's behaviour with build metadata may vary; stripping it before parsing is the safest approach regardless of library internals.