Skip to content

Fix iOS upgrade popup reappearing after update due to build metadata in version string (fixes: #480)#552

Closed
mem-5514-tahara wants to merge 1 commit into
larryaasen:mainfrom
mem-5514-tahara:fix/ios-popup-persists-after-update
Closed

Fix iOS upgrade popup reappearing after update due to build metadata in version string (fixes: #480)#552
mem-5514-tahara wants to merge 1 commit into
larryaasen:mainfrom
mem-5514-tahara:fix/ios-popup-persists-after-update

Conversation

@mem-5514-tahara

Copy link
Copy Markdown

@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.version can 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). Because Version.parse('1.2.3+45') produces a value that compares as greater than Version.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, so 1.2.3+45 and 1.2.3 are 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. Direct Version.parse() call sites in upgrader.dart have been migrated to use parseVersion(). Call sites in upgrade_store_controller.dart and appcast.dart (which cannot import Upgrader without a circular dependency) apply the same version.split('+').first normalization inline.

A debug log line is emitted whenever normalization occurs:

upgrader: version normalized: '1.2.3+45' → '1.2.3'

Changes

  • lib/src/upgrader.dart — updated parseVersion() to strip build metadata; migrated all direct Version.parse() call sites
  • lib/src/upgrade_store_controller.dart — normalized store-supplied version strings in UpgraderAppStore, UpgraderPlayStore, and UpgraderAppcastStore
  • lib/src/appcast.dart — normalized version strings used in best-item selection and host-support checks

Tests

Added 4 tests in test/upgrader_test.dart:

Test Expected
parseVersion('1.2.3+45') equals Version.parse('1.2.3'), not greater
parseVersion('1.2.3') unchanged, no regression
Installed 1.2.3+45, store 1.2.3 isUpdateAvailable()false
Installed 1.2.2+99, store 1.2.3 isUpdateAvailable()true

Full test suite passes: flutter test --coverage.

Notes

  • No public API changes.
  • The package:version library's behaviour with build metadata may vary; stripping it before parsing is the safest approach regardless of library internals.
  • Play Store and Appcast paths receive the same normalization for consistency, though the primary motivation is the iOS/App Store case.

…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.
@larryaasen

Copy link
Copy Markdown
Owner

@mem-5514-tahara Thanks for sharing this PR with the community. I would like to see more evidence in support of the root cause.

@larryaasen larryaasen added enhancement New feature or request need more information Further information is requested labels Jun 3, 2026
@mem-5514-tahara

Copy link
Copy Markdown
Author

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 package:version locally, and I ended up completely disproving my own hypothesis.

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:

Version.parse("1.0") => 1.0.0
Version.parse("1.2.3.4") => 1.2.3
Version.parse("1.2.3+45") => 1.2.3+45
Version.parse("1.2.3") => 1.2.3
Version.parse("5.6") => 5.6.0
installed == store : true
installed > store  : false

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request need more information Further information is requested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Once after updating the new version, and opening the app version for first time in IOS, the Popup still comes.

2 participants