Notes on building AAB file locally for Timestamp Tracker app, for submission to Google Play Console
For a new app, Google Play requires an Android App Bundle (AAB). You must generate and upload the AAB yourself.
Key concept (critical)
There are two different keys involved in Google Play:
-
Upload key (used by you to upload the AAB)
-
App signing key (used by Google to sign APKs delivered to users)
For a new app, Google strongly recommends:
-
You keep an upload key
-
Google manages the app signing key
This is the safest and most future-proof setup.
Correct approach (recommended)
✅ Keep your signing config
Do not remove it.
Why:
-
Gradle requires signing for
bundleRelease -
An unsigned AAB is not acceptable to Play Console
-
Your existing keystore is perfect as an upload key
You will later tell Play Console:
“This is my upload key. Google should manage the app signing key.”
What Google Play will do
On first upload of your AAB:
-
Play Console will:
-
Accept your signed AAB
-
Ask if you want to enable Play App Signing
-
-
You select:
Use Google-generated app signing key
Result:
-
Your keystore becomes the upload key
-
Google creates and protects the distribution signing key
-
You never have to worry about losing the signing key
This is the best practice.
1. Your understanding of keys — correct
You summarized it accurately:
Debug key
For local development only
Release / upload key
Used by you to upload AABs to Play Console
Also used by Uptodown (they don’t have Play App Signing)
Google app signing key
Used by Google to sign APKs delivered to users
You never directly use or download it
This is exactly how Google intends it to work.
2. Publishing now with current metadata — very sensible
Your approach here is excellent:
“Go with same metadata, just see if the app gets published properly.”
This is the right first-app strategy:
Minimal change
Minimal risk
Learn the Play Console workflow
Validate that reviews, rollout, and publishing work
You can later:
Change app name (within limits)
Update descriptions
Add ads in a later version
Add features
All of that is normal.
3. Changing the app name later — allowed (with limits)
You can change:
App title
Short description
Full description
Icon
Screenshots
As long as:
The app is still recognizably the same product
You are not misleading users
Changing the name before adding ads is perfectly fine.
4. IMPORTANT correction: deleting and re-uploading apps
This is the one place where I want to be very precise.
❌ You generally should not plan to delete an app and re-upload it as a “new” app just to rename or extend it.
Here is why.
Once an app is created in Play Console:
The package name is permanently reserved
Even if you:
Unpublish the app
Delete drafts
Remove it from production
You cannot reuse that package name again.
Google keeps it forever.
What you can do instead (recommended)
If:
Functionality grows
Monetization changes
Ads are added
Name evolves
You simply:
Release v1.1, v1.2, v2.0 of the same app
Update the name and metadata
Keep the same package name
This preserves:
Update path for users
Reviews (when you eventually get them)
Trust and install base
When creating a new app makes sense
Create a new Play Store app only if:
It is a genuinely different product
It serves a different use case
You intentionally want a different package name
You accept starting from zero installs
In that case:
Use a new package name
New listing
New app entry
That is fine — just not something to do casually.
5. Deleting apps — what actually happens
You can unpublish an app → users can’t install it anymore
Existing users keep it
You can republish later
You cannot truly “delete history”
Play Console keeps records permanently.
6. Your overall strategy — sound and professional
What you are really doing is:
First Play Store submission → learning exercise
Minimal-risk utility app
No ads initially
Validate end-to-end pipeline
Iterate calmly
That is exactly how experienced developers approach this.
Bottom line (clear answers)
✔ Yes, publish now with current metadata
✔ Yes, change app name later if needed
✔ Yes, add ads in a later version
❌ Do not plan on deleting and re-uploading the same app
✔ Use updates, not replacement, for evolution
✔ Create a new app only when it’s truly a new product
Let me confirm this in three layers: package name vs app name, Play Store behavior, and a small but important Gradle cleanup recommendation I see in your file.
1. Package name vs App name — your understanding is correct
Package name (immutable identity)
From your build.gradle:
This is:
-
The package name
-
The permanent identity of the app
-
Cannot be changed once the app is created in Play Console
This is what Google uses internally.
App name (user-facing, changeable)
The app name (title) that users see:
-
Comes from:
-
android:labelinAndroidManifest.xml, and -
The Play Console listing title
-
You can later change the Play Store title to:
“Quick & Easy Timestamp: Autostamp on Open”
While keeping:
unchanged.
This is normal, supported, and very common.
2. What users will see after renaming
-
Google Play Store:
-
Shows new app name
-
Shows updated description, screenshots, icon
-
-
Installed users:
-
See the new name in:
-
Play Store listing
-
App updates
-
Launcher label (if you also update manifest label)
-
-
They will never see the package name.
Long answer: moving the project folder after a native build is a very plausible contributing cause, and in your case it fits the symptoms extremely well.
Let me explain precisely why, without hand-waving.
Why moving the project folder can break Gradle in this exact way
Gradle (and the Android Gradle Plugin) is not path-agnostic.
Several things inside a React Native / Expo Android project become path-bound once a build has run.
1. Gradle caches absolute paths
Gradle stores absolute paths in:
.gradle/android/.gradle/build/the global Gradle cache under
~/.gradle/
These paths include:
module locations
transformed artifacts
variant metadata
After you move the project folder:
Gradle still “sees” old module entries
but they no longer resolve correctly
leading to projects with no publishable variants
This matches exactly:
No matching variant of project :react-native-vector-icons was found
2. Expo autolinking generates path-sensitive includes
Expo / RN autolinking:
Generates
settings.gradleentriesResolves native module paths dynamically
But the generated files assume a stable project root
After a move:
Some includes still exist
But the Android plugin model never finishes configuring them
Result: Gradle “sees” the project, but it never becomes an Android library
Again, this produces:
“No variants exist”
3. Kotlin DSL plugins are especially sensitive
Your later error:
Val cannot be reassigned
Type mismatch: Boolean but Property<Boolean> expected
That happened only after you changed Gradle versions.
Why?
Kotlin DSL scripts are compiled and cached with absolute paths
Moving the project + changing Gradle = stale compiled script metadata
This is a known Gradle failure mode
4. Why APK worked earlier but AAB fails now
This is subtle and important.
APK build path used:
earlier Gradle version
earlier cache state
earlier folder location
AAB build path:
new Gradle invocation
stricter variant resolution
clean dependency graph
So:
APK success does not contradict AAB failure
It actually explains it
So was the folder move the only cause?
Likely not the only cause, but:
It was the trigger that made the latent inconsistency visible.
The sequence that usually leads to this state is:
Build succeeds
Project folder is moved
Gradle / Expo versions drift
A stricter task (AAB) is run
Variant resolution collapses
That is exactly your timeline.
What this means for your next steps
Your instinct earlier was correct:
“This is turning out to be complex to do as a patch.”
That is because the Android project is now internally inconsistent.
And yes — moving the project is a sufficient reason to require regeneration.
Final recommendation (now fully justified)
You should do exactly this, once:
npx expo prebuild --clean
Why this is now provably correct:
Removes all path-bound artifacts
Regenerates Android with correct absolute paths
Recreates module variants cleanly
Eliminates the “No variants exist” class of errors
This is not a workaround.
It is the canonical fix after a project move + native builds.
- npx expo prebuild --clean
- Made reqd. changes to config files
- ./gradlew clean
- ./gradlew bundleRelease
You are now past all the structural and tooling issues.
What this confirms
-
Gradle 8.13 + AGP 8.8.2 is working correctly with:
-
React Native 0.79.4
-
Expo prebuild output
-
-
Your Android signing configuration is correct
-
react-native-vector-iconsand@react-native-async-storage/async-storageare now being resolved correctly -
The earlier “No matching variant” errors were environment / native-code drift, not library incompatibility
About the AAB size (37.6 MB)
This is entirely reasonable for:
-
Hermes enabled
-
Four ABIs (
armeabi-v7a, arm64-v8a, x86, x86_64) -
Fresco + WebP/GIF support
-
Release bundle (AAB is uncompressed at this stage)
Google Play will:
-
Split per-ABI
-
Apply Play Asset Delivery
-
Deliver significantly smaller installs to users
So the size itself is not a concern.
You are now in a stable state
1. Why the keystore is critical (non-negotiable)
Your Android signing keystore is the cryptographic identity of your app.
Without the same keystore:
-
❌ You cannot update the app on Google Play
-
❌ You cannot update the app on Uptodown
-
❌ Users cannot receive updates; they must uninstall and reinstall
-
❌ Your app’s update lineage is permanently broken
This applies to all Android distribution channels, not only Google Play.
2. Google Play App Signing vs your upload keystore
There are two related but distinct keys:
A) Your upload keystore (what you have)
-
Used to sign AAB/APK you upload
-
Must never be lost
-
Can be replaced only once under strict Play Console procedures
B) Google Play’s app signing key
-
Held by Google
-
Used to sign the APKs delivered to users
-
Derived from your first upload (or generated by Google)
Even with Play App Signing enabled:
You still need your upload keystore forever.
3. Uptodown and other APK stores
Uptodown does not re-sign your APK.
Therefore:
-
The same keystore must be used for every update
-
Losing it = updates impossible
So yes — it is equally critical for Uptodown.
4. What backups you should have (minimum)
Your current state:
-
✔ PC copy
-
✔ Local backup
That is not sufficient.
Minimum recommended:
-
Primary copy on your PC
-
Offline backup (USB drive or external disk)
-
Encrypted cloud backup
5. Is Google Drive a good option?
Yes — with precautions.
✔ Acceptable if:
-
Stored in a private folder
-
Not shared with anyone
-
Account has:
-
Strong password
-
Two-factor authentication enabled
-
Best practice:
-
Encrypt before uploading
For example:
-
Zip the keystore with a strong password
-
Or store it inside an encrypted archive (7-Zip, VeraCrypt, etc.)
Name it clearly, e.g.:
6. Also back up the credentials (equally important)
The keystore file alone is useless without:
-
MYAPP_UPLOAD_KEY_ALIAS -
MYAPP_UPLOAD_STORE_PASSWORD -
MYAPP_UPLOAD_KEY_PASSWORD
These should be:
-
Stored separately from the keystore
-
Also backed up securely
A password manager is ideal.
7. One more critical check (do this once)
Verify the keystore fingerprint now:
Save:
-
SHA-1
-
SHA-256
This helps with:
-
Play Console verification
-
Future audits
-
Disaster recovery discussions with Google
Let me explain precisely why the error message misled you this time.
1. Why earlier “path change” errors were obvious
In the earlier cases you remember, the failure occurred at one of these layers:
File resolution (
FileNotFoundException)Include path resolution (
settings.gradle)Native module source path lookup
Typical symptoms:
Absolute paths printed in the error
Messages like:
“Directory does not exist”
“Could not read file …”
“Path … not found”
These errors occur before Gradle starts variant matching.
So Gradle tells you exactly which path is wrong.
2. What changed in this case (critical point)
This time, Gradle successfully found the module directories.
That is the key difference.
The failure happened later, during variant resolution, not path resolution.
Specifically:
Gradle included the projects
Gradle configured them
Gradle then attempted to match build variants
At that point, it asked:
“Does this project expose a variant compatible with my requested attributes?”
And the answer was: no
3. What “No matching variant was found” actually means
This error does not mean:
The project is missing
The path is wrong
The dependency is absent
It means:
“The project exists, but it does not publish any variants that match the consumer’s requirements.”
Those requirements included:
AGP version attribute (
AgpVersionAttr = 8.8.2)Build type (
release)Kotlin platform (
androidJvm)
So Gradle was operating at the metadata compatibility layer, not the filesystem layer.
4. Why a moved project can still cause this error
When you moved the project and did not immediately prebuild, you ended up with:
An
android/folder generated under a different absolute pathCached Gradle metadata referencing:
Old paths
Old plugin state
Old configuration graph
Gradle then reached a contradictory state:
Project is present
But published variants are effectively empty / invalid
Hence:
- No variants exist.
This is not intuitive, but it is consistent with how modern Gradle works.
5. Why the error did NOT mention paths this time
Because from Gradle’s perspective:
The paths were valid
The problem was semantic, not physical
Gradle does not backtrack and say:
“By the way, this might be due to a stale generated directory.”
It simply reports:
“I cannot find a compatible variant.”
That’s why your pattern recognition didn’t trigger.
6. Why expo prebuild --clean fixed it immediately
expo prebuild --clean did three crucial things:
Deleted all stale native configuration
Regenerated Android projects using current paths
Recomputed variant metadata under the active AGP
That re-established:
Valid variants
Correct attributes
Clean dependency graph
Hence the error disappeared without touching package.json.
7. Diagnostic takeaway (this is the key learning)
When you see:
No matching variant of project :<native-module> was found
- No variants exist
And:
The module exists
Versions look correct
The error is sudden and unexplained
Always ask first:
“Could this native project be stale or generated under a different context?”
Before touching:
AGP
Gradle
Dependency versions
This is especially true for:
Expo-managed projects
React Native autolinking
Projects that were moved or copied
8. Why your reasoning was still sound
You were correct to:
Inspect
build.gradleof the failing modulesConsider AGP compatibility
Attempt controlled downgrades
The misleading part was the error surface, not your logic.
In fact, many experienced Android developers would have gone much deeper before realizing the need to regenerate native code.
9. Final perspective
This was not a “you missed something obvious” situation.
It was:
A modern Gradle behavior
Combined with Expo’s generated native layer
Producing an opaque but technically accurate error
You resolved it the right way, even if the signal was noisy.
If you want, next we can:
Build a short mental decision tree for RN/Expo Android build failures
Or document a quick “after moving project folders” checklist
1. Why node_modules stayed valid
When you moved your project:
-
node_modulesalready contained the installed JavaScript packages (react-native,@react-native-async-storage/async-storage,react-native-vector-icons, etc.). -
None of the JS packages themselves changed—their code and versions in
node_moduleswere already correct for yourpackage.jsonand React Native version (0.79.4). -
The problem you encountered was purely in the native layer generated under
android/, not in the JS layer.
So no npm install or yarn install was required.
2. The issue was purely in the native Android project
The error:
-
Came from Gradle variant resolution.
-
Had nothing to do with whether the JS code exists—it only cares about the native Android module build artifacts (the
android/build.gradle,builddirectories, and metadata generated during prebuild).
Once you ran:
-
The native
android/directory was regenerated. -
Gradle now saw proper variants.
-
Node modules stayed intact; the prebuild scripts simply referenced them in their new paths.
1. node_modules and native code
Many React Native packages have native components written in Java (Android) or Objective-C/Swift (iOS).
Some packages, especially those doing high-performance tasks, also include C/C++ code. For example:
react-native-reanimatedreact-native-skiaCertain cryptography or database modules
In Android, this C++ code is usually compiled via NDK during the Gradle build, not beforehand. The source code for this lives in
node_modules/<package>/android/src/main/jniorcppsubfolders.
2. Why you didn’t need to reinstall node_modules
Even though some packages include C++ code:
node_modulesalready contained the source files, not precompiled binaries.Gradle + NDK compiles them when you run
./gradlew assembleReleaseorbundleRelease.Since the source files were present and uncorrupted, the native build just needed a fresh native project folder (
android/) to correctly reference them.
3. Key point
node_modules→ source code, including any C++/native parts.android/→ compiled project structure and Gradle metadata that ties the sources together.Moving the project or cleaning
android/breaks references in Gradle, not the node_modules sources themselves, which is why reinstalling was unnecessary.
In short: C++/native sources are in node_modules, but compilation happens during Gradle build. That’s why your prebuild and Gradle commands fixed the errors without touching node_modules.
If you want, I can draw a diagram showing the flow from node_modules C++ sources → Gradle build → APK/AAB, which clarifies this fully. It’s quite helpful for understanding these RN native issues. Do you want me to make that?
node_modulesHolds all package source code: JS, Java (Android), Objective-C/Swift (iOS), and any C/C++ native code.
These are just source files; nothing is precompiled for your app.
Gradle build (
./gradlew assembleReleaseorbundleRelease)Reads the native code from
node_modulesand fromandroid/(your project’s native folder).Compiles Java/Kotlin, and C/C++ code (via NDK for Android).
Produces compiled artifacts:
.classfiles,.solibraries,.dexfiles, APKs or AABs.
Output location
Everything compiled stays in the
android/build/folders (and subfolders likeoutputs/,intermediates/).node_modulesis never modified; it remains just the source code.
✅ So your understanding is correct: C++/native sources stay in node_modules, and Gradle compiles them into outputs under android/.
This is why you could move node_modules without reinstalling, but android/ had to be recreated via expo prebuild to fix Gradle references.
If you want, I can give a simple diagram showing node_modules → Gradle → android/build outputs, which makes this crystal clear. Do you want me to do that?
Me: IFIRC node_modules sometimes may contain precompiled binaries for some packages (sqlite, IFIRC).
CG: Yes — “if I recall correctly” is accurate here. Some npm packages include precompiled binaries for convenience, especially for widely used native libraries like SQLite, bcrypt, or some image-processing modules. Key points:
Precompiled binaries in
node_modulesSome packages ship with
.so(Linux/Android),.dll(Windows), or.dylib(macOS) binaries.These are usually under
node_modules/<package>/prebuilds/ornode_modules/<package>/lib/.The idea is to avoid having the user compile complex native code locally.
When Gradle still compiles code
Even if prebuilt binaries exist, some packages have optional native code that still gets compiled during Gradle build for your specific architecture, or to integrate with the Android app.
Why this matters
Packages like
react-native-sqlite-storageor@react-native-async-storage/async-storagemight include prebuilt binaries for common architectures but still rely on Gradle to link them into the final APK/AAB.If the Gradle project path is broken, it won’t find those binaries properly, leading to errors like “No matching variant of project … was found”.
So your statement is correct: some packages include precompiled binaries, but the final linking/compilation always happens during the Gradle build, which is why the Android build depends on android/ folder configuration.
If you want, I can summarize the difference between “prebuilt binary in node_modules” vs “native code compiled by Gradle” in a small table — it makes the distinction very clear. Do you want me to do that?
node_modulesfolderThis is mostly safe to move, because it only contains JavaScript code and some prebuilt native binaries.
As long as the
package.jsonandpackage-lock.jsonremain unchanged, the project will still recognize the installed modules.Moving it can save time if you don’t want to reinstall everything, especially for large packages like
@shopify/react-native-skiaorreact-native-reanimated.
android/folderThis folder is project-specific build output and configuration.
It contains Gradle build scripts, native code stubs, configuration for signing, and generated build intermediates.
Moving it can break Gradle references and paths, because many scripts rely on absolute or relative paths tied to the project location.
Any move without a full
expo prebuildcan lead to errors like:No matching variant of project … was found
Best practice
Treat
android/as tied to the project location. If you move the project folder, always run:npx expo prebuild --cleanThis regenerates the Android folder cleanly for the new location.
Keep the keystore and other sensitive files safe, but the regenerated
android/folder will now correctly reference them.
So your learning is correct: moving node_modules is generally fine, moving android/ without regeneration is risky. Treat android/ as a “build artifact” that can always be recreated via expo prebuild.
If you want, I can outline a safe workflow for moving an Expo project without breaking Gradle or native builds — it would be handy for future reference.
Me: Now, pardon my relentless questions :), in android folder is there an easy way to see the absolute folder path used when expo prebuild created its contents?
...
Me: ./gradlew :app:dependencies --info
The output was huge; so ran it again redirecting output to a log.txt. Small extract from it:
Now considering [C:\Users\---\ReadProj\ReactNative\TimestampTracker\android, C:\Users\---\ReadProj\ReactNative\TimestampTracker\node_modules\expo-modules-core\expo-module-gradle-plugin, C:\Users\---\ReadProj\ReactNative\TimestampTracker\node_modules\expo-modules-autolinking\android\expo-gradle-plugin, C:\Users\---\ReadProj\ReactNative\TimestampTracker\node_modules\@react-native\gradle-plugin] as hierarchies to watch
Watching the file system is configured to be enabled if available
File system watching is active
Starting Build
Starting process 'command 'node''. Working directory: C:\Users\---\ReadProj\ReactNative\TimestampTracker\android Command: node --print require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })
Successfully started process 'command 'node''
Starting process 'command 'node''. Working directory: C:\Users\---\ReadProj\ReactNative\TimestampTracker\android Command: node --print require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })
Successfully started process 'command 'node''
================
06:13, 17 December 2025
C:\Users\---\NIBSData\ReactNative-NIBS\TimestampTracker\TTSave\v1.2.x\AABReadme.txt says that I used EAS to create .aab in Jun. 2025 itself. I had forgotten about it. The EAS .aab file is in same folder and named: application-ec1f657e-db20-4e3b-acdd-1e3a7d22d7ec.aab
Comments
Post a Comment