Notes on building AAB file locally for Timestamp Tracker app, for submission to Google Play Console

Last updated on 18 Dec. 2025

Me: ... I want to publish Timestamp Tracker. As I have already published it on Uptodown - https://timestamp-tracker.en.uptodown.com/android (has 21 downloads but no reviews) - I have all the metadata and even screenshots ready.
Now, I have the Android APKs. Can I use them to publish to Google Play Store with Google Play store doing the work of converting them to play store format (AAB or something)?

CG: Short answer: No — Google Play will not take your existing APKs and convert them for you for production release.

For a new app, Google Play requires an Android App Bundle (AAB). You must generate and upload the AAB yourself.

...
Me: I did a local build (not eas) for the APK.
I want to do local build for AAB as well (I want to know how to be independent of eas where I sometimes faced very long wait times - perhaps I had exceeded some free tier quota).
Now I already have my own release signature in the related gradle config files.
So should I simply remove that entry before running:
./gradlew bundleRelease

CG: 

Key concept (critical)

There are two different keys involved in Google Play:

  1. Upload key (used by you to upload the AAB)

  2. 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.

Me: Quite a lot of keys - debug key, release key which becomes upload key for Play Store but is viewed as release key by Uptodown, and then Google's app signing key. but I think I got it now from your excellent explanation.
I may want to change app name later on (especially before I add Ads).
But now I want to go with same metadata and just see if the app gets published properly.
Later, I think I should even be able to delete this app from Play store and then upload a new app with a different name and perhaps slightly more functionality. am i on right track?

CG: You are mostly on the right track, and your understanding of the keys is now correct. Let me confirm what is right, and very carefully correct one important assumption about deleting and re-uploading apps, because that is where many developers get surprised.

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:

  1. First Play Store submission → learning exercise

  2. Minimal-risk utility app

  3. No ads initially

  4. Validate end-to-end pipeline

  5. 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

...
Me: Got the point: Do not plan on deleting and re-uploading the same app
Noted that Google retains the info. even after unpublishing the app and so packagename is reserved and cannot be reused.
Also noted the reviews of old app being effectively lost for new app if app name is different.

...
Me: The package name I think is: com.ravisiyer.timestamptracker (applicationId) Even if package name remains same if I change the app name from "Timestamp Tracker" to, say, "Quick & Easy Timestamp: Autostamp on open", the app store will show the new name - only underlying package name will remain com.ravisiyer.timestamptracker Is my understanding right?

CG: Yes — your understanding is 100% correct, both conceptually and in terms of how Google Play actually behaves.

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:

applicationId 'com.ravisiyer.timestamptracker'

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:label in AndroidManifest.xml, and

    • The Play Console listing title

You can later change the Play Store title to:

“Quick & Easy Timestamp: Autostamp on Open”

While keeping:

com.ravisiyer.timestamptracker

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.

===================================
===================================

I first tried building AAB file using './gradlew bundleRelease' but in the project folder which I had moved to a ReadProj directory where I keep projects whose source code I am not changing. That created a problem for gradlew. It took quite some time for me to figure this issue out. Some info. about the issue is given below for any future reference I may need.

Me: ... with this error:
[Incubating] Problems report is available at: file:///C:/Users/{username}/ReadProj/ReactNative/TimestampTracker/android/build/reports/problems/problems-report.html

FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':app:buildReleasePreBundle'.
> Could not resolve all dependencies for configuration ':app:releaseRuntimeClasspath'.
   > Could not resolve project :react-native-async-storage_async-storage.
     Required by:
         project :app
      > No matching variant of project :react-native-async-storage_async-storage was found. The consumer was configured to find a library for use during runtime, preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.AgpVersionAttr' with value '8.8.2', attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' but:
          - No variants exist.
   > Could not resolve project :react-native-vector-icons.
     Required by:
         project :app
      > No matching variant of project :react-native-vector-icons was found. The consumer was configured to find a library for use during runtime, preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.AgpVersionAttr' with value '8.8.2', attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'release', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' but:
          - No variants exist.

* Try:
> Creating consumable variants is explained in more detail at https://docs.gradle.org/8.13/userguide/declaring_dependencies.html#sec:resolvable-consumable-configs.
> Review the variant matching algorithm at https://docs.gradle.org/8.13/userguide/variant_attributes.html#sec:abm_algorithm.

CG suggested it could be "about Android Gradle Plugin (AGP) compatibility." It suggested downgrading AGP and Gradle. But I could even do release APK builds without a problem in the past with same folder contents (but in a different location in the file system). So I spent a lot of time trying to understand the basis of that suggestion and to know in general how to diagnose such issues. But I did not get a clear idea.

Eventually I tried the downgrading AGP and Gradle suggestion. But that gave some other error.

...

Me: Now I recall, I had moved the project folder to another location after APK build some time back. Could that result in these errors?

CG: Short answer: yes — absolutely.

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.gradle entries

  • Resolves 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:

  1. Build succeeds

  2. Project folder is moved

  3. Gradle / Expo versions drift

  4. A stricter task (AAB) is run

  5. 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.

----
Afterwards it was quite a straightforward affair to do the build of AAB file. Main steps:
  • npx expo prebuild --clean
  • Made reqd. changes to config files
  • ./gradlew clean
  • ./gradlew bundleRelease
app-release.aab of 37.6 MB got created.

CG: Excellent. That is a clean, correct outcome.

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-icons and @react-native-async-storage/async-storage are 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

...

[The below content may be an exhaustive approach. I wanted to record it even if I may not follow it entirely.]
CG: ...

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:

  1. Primary copy on your PC

  2. Offline backup (USB drive or external disk)

  3. 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.:

timestamptracker-upload-keystore.enc

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:

keytool -list -v -keystore my-release-key.keystore

Save:

  • SHA-1

  • SHA-256

This helps with:

  • Play Console verification

  • Future audits

  • Disaster recovery discussions with Google

 ...

Me: About why I could not spot that the earlier error "No matching variant of project :react-native-async-storage_async-storage was found" ....

I had faced this issue of RN build breaking due to path change but in that case, the error message clearly showed the path that it was looking for which did not exist. Seeing that path was enough for me to know that it was a path change issue.

But this time the error message did not say that. Your comments please.

CG: This is an excellent diagnostic question, and your intuition is correct: this failure felt different because it was different at the Gradle level, even though the root cause (stale / invalid native state after a move) was ultimately similar.

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 path

  • Cached 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:

  1. Deleted all stale native configuration

  2. Regenerated Android projects using current paths

  3. 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.gradle of the failing modules

  • Consider 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


Me: I did not have to reinstall node_modules. Your comments please.

CG: That makes perfect sense in this situation. Here’s why:

1. Why node_modules stayed valid

When you moved your project:

  • node_modules already 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_modules were already correct for your package.json and 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:

No matching variant of project :react-native-async-storage_async-storage was found
  • 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, build directories, and metadata generated during prebuild).

Once you ran:

npx expo prebuild --clean
  • 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.



Me: the node_modules folder would also hold some C++ code isn't it (for some native specific packages)

CG: Yes, exactly—but with some nuance. Here's the breakdown:

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-reanimated

    • react-native-skia

    • Certain 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/jni or cpp subfolders.


2. Why you didn’t need to reinstall node_modules

Even though some packages include C++ code:

  • node_modules already contained the source files, not precompiled binaries.

  • Gradle + NDK compiles them when you run ./gradlew assembleRelease or bundleRelease.

  • 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_modulessource 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?

Me: In short: C++/native sources are in node_modules, but compilation happens during Gradle build.
---- and this Gradle build puts its output files only in android folder and not in node_modules. Am I on right track?

CG: Exactly right. Here’s the precise flow:
  1. node_modules

    • Holds 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.

  2. Gradle build (./gradlew assembleRelease or bundleRelease)

    • Reads the native code from node_modules and from android/ (your project’s native folder).

    • Compiles Java/Kotlin, and C/C++ code (via NDK for Android).

    • Produces compiled artifacts: .class files, .so libraries, .dex files, APKs or AABs.

  3. Output location

    • Everything compiled stays in the android/build/ folders (and subfolders like outputs/, intermediates/).

    • node_modules is 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:

  1. Precompiled binaries in node_modules

    • Some packages ship with .so (Linux/Android), .dll (Windows), or .dylib (macOS) binaries.

    • These are usually under node_modules/<package>/prebuilds/ or node_modules/<package>/lib/.

    • The idea is to avoid having the user compile complex native code locally.

  2. 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.

  3. Why this matters

    • Packages like react-native-sqlite-storage or @react-native-async-storage/async-storage might 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?

Me: I moved the project folder after the project had become stable to a ReadProj folder which is out of regular backup script. I thought I could save on node_modules reinstallation - which seems to be right - and also on some android folder stuff - there I was wrong. So a learning for me here is that while I can move node_modules folder of a project, I should simply not move the android folder. That will ensure that I will do a fresh expo prebuild every time I want to modify/rebuild/reuse the project. Ur comments please.

CG: Exactly — your takeaway is spot on. To summarize and clarify:
  1. node_modules folder

    • This is mostly safe to move, because it only contains JavaScript code and some prebuilt native binaries.

    • As long as the package.json and package-lock.json remain 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-skia or react-native-reanimated.

  2. android/ folder

    • This 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 prebuild can lead to errors like:

      No matching variant of project … was found

  3. Best practice

    • Treat android/ as tied to the project location. If you move the project folder, always run:

      npx expo prebuild --clean
      

      This 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