Rebuilding an App? Your Version Numbers Don't Start Over

Rebuilding an App? Your Version Numbers Don't Start Over
I was rebuilding an app from scratch, so I gave the first Android build the obvious version code: 1.
Android refused to install it.
The message was blunt: the new APK was a downgrade because version code 1 was older than version code 7 already installed on the device.
The source code was new. The project folder was new. The app was not.
It still had the same identity as the version users already owned, which meant it also inherited that version's history.
A rewrite is still an update
When you create a fresh mobile project, the tooling naturally starts at version 1. That makes sense for a new product.
It does not make sense when the new project will replace an existing app in Google Play or Apple's App Store.
The stores and operating systems do not care how much code you rewrote. They look at the app's identity and version:
- Android uses the package name and an increasing version code.
- iOS uses the bundle identifier and an increasing build number.
- Existing users expect the update to install over the app they already have.
If the identity matches but the version goes backwards, the device treats the new build as older software.
The fix was simple. The rebuild needed a version code above the live release from its very first install test.
The lesson was larger: a ground-up rebuild is a continuation, not a clean slate.
Then I installed the wrong build
The version number was only the first collision with the old app.
During iOS simulator testing, the build completed successfully and the app launched. I took a screenshot as proof.
The screenshot showed the old app.
The install command searched DerivedData using a wildcard. Three matching build folders existed, including two left behind by the previous codebase. The command found a valid app bundle and installed it, but not the one I had just built.
Every individual step appeared to succeed:
- The new source compiled.
- An app bundle was found.
- The simulator accepted the installation.
- The app launched.
The combined result was still wrong because the artifact search was ambiguous.
I replaced the wildcard with the exact output path from the build command. I also kept the screenshot check, because without it I might have accepted a successful exit code as proof.
A green build proves that something compiled. It does not prove that the thing on the screen is your build.
Old installations are useful evidence
There was one more mistake hiding in the cleanup process.
To get past an installation problem, I removed the old app from a test device and installed the rebuild cleanly. That worked, but it also deleted the saved data from the previous version.
That data would have been an ideal migration test.
Real users do not receive a clean install when they update. They bring settings, saved records, preferences, and whatever odd state years of normal use have created.
An old device with genuine data is not clutter. It is a test fixture.
Before uninstalling an existing app during a rebuild, I now ask:
- What data is stored on this device?
- Can it be backed up?
- Has the new version been tested as an update over it?
- Do I have another device or simulator that preserves the old state?
Cleaning the environment can remove the exact evidence needed to prove the migration works.
What a rebuild inherits
The obvious inheritance is the store listing, but it is not the only one.
A replacement app also inherits:
- bundle and package identifiers
- version history
- signing and release configuration
- existing installations
- saved user data
- deep links and external integrations
- user expectations
- old build artifacts on development machines
Some of these are assets. Some are constraints. Ignoring either category makes the rebuild riskier.
This is why I no longer describe a replacement app as "starting again." The implementation starts again. The product does not.
My day-one rebuild checklist
Before the first device test, I now verify:
- The new project uses the exact existing app identifier.
- Its version and build numbers are above the live release.
- Signing points to the correct production identity.
- Build and install commands use exact artifact paths.
- The running app has a visible marker I can verify.
- At least one old installation with data is preserved for migration testing.
- Clean-install and upgrade-install tests are treated as separate cases.
None of these steps is complicated. They are easy to miss because scaffolding tools present a new project as a new beginning.
The store does not see a beginning. The device does not see a beginning. Your users certainly do not.
A rebuild can replace every line of code and still carry years of history into its first launch.