Every Build Reported the Same Version (Flutter iOS)

Pubspec said 2.4.0+117, the app showed 2.3.9. The culprit was not Flutter: two hardcoded files nobody ever looks at, and a lesson that sticks for years.

The Symptom: A Version That Refuses to Change

One of the production apps I've worked on shows the build version on its settings screen. I bump pubspec.yaml to 2.4.0+117, build, ship to TestFlight. Open settings: 2.3.9 (98).

Clean the cache. Build again. 2.3.9 (98). Every build, for weeks, reported exactly the same version, no matter what pubspec said.

Where an App Actually Gets Its Version Number

It's worth understanding the whole chain, because the bug can live in any link:

  1. pubspec.yaml has version: 2.4.0+117: the part before the plus is the marketing version, after it the build number.
  2. At build time Flutter turns this into FLUTTER_BUILD_NAME and FLUTTER_BUILD_NUMBER in Generated.xcconfig.
  3. Xcode maps those into project settings: MARKETING_VERSION and CURRENT_PROJECT_VERSION.
  4. Those land in Info.plist as CFBundleShortVersionString and CFBundleVersion.
  5. At runtime, package_info_plus reads the values from the built Info.plist, not from pubspec.

Point five is the key: what the user sees comes from the Info.plist inside the binary. Pubspec is just the start of the chain, and every link after it can override it.

The Cause: Two Hardcoded Values Nobody Ever Looks At

In that project's ios/Runner/Info.plist, someone once hardcoded the values (probably while debugging something entirely unrelated):

And in project.pbxproj, an additional hardcoded CURRENT_PROJECT_VERSION = 98. The result: the entire chain from pubspec worked correctly right up to the final step, where the hardcode cut everything off. Flutter raised no error, Xcode raised no error, the build passed. Just with an eternal version 2.3.9.

The Fix, and One Trap

The fix is restoring the variables:

The trap: in pbxproj the $(...) value must be quoted. Without quotes Xcode considers the project file corrupted ("project file is damaged") and refuses to open the project at all. My first attempt ended exactly there.

The second lesson is less technical: I fixed it locally, tested it... and didn't commit. A week later the bug "came back", because it had never left the repo. A fix that isn't in git doesn't exist.

What Stays With You