Skip to content

Instantly share code, notes, and snippets.

@nikdo
Last active December 17, 2020 08:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikdo/0d294aeea72074e4dcab1660431a7285 to your computer and use it in GitHub Desktop.
Save nikdo/0d294aeea72074e4dcab1660431a7285 to your computer and use it in GitHub Desktop.

The only correct way how to create unsigned APK with React Native

TLDR;

Within andorid/app/build.gradle file, define staging build type, set bundleInStaging to true and produce unsigned APK by running ./gradlew assembleStaging

Too many cooks

There are many different suggestions on how to use react-native bundle command to produce React Native JavaScript bundle together with other resources and put them to the correct place so that ./assembleDebug can take them and create unsigned APK.

Some of them use android/app/src as a target location which results in having those files in an inappropriate place and in source control with React Native default .gitignore. But they end up in the APK because this is where native Android apps put their static assets.

The rest of the suggestions, including official React Native CLI docs, put them correctly to the android/app/build folder. But most of them do not result in JavaScript bundle being part of the APK with the latest React Native version.

React Native Gradle tasks FTW

The answer lies in react.gradle file within react-native repository, where official Gradle tasks for bundling and copying reside.

bundleDebugJsAndAssets Gradle task puts it's output by default to android/app/build/generated directory:

  • JS bundle: android/app/build/generated/assets/react/debug/index.android.bundle
  • Resources: android/app/build/generated/res/react/debug

While this is the target location for the resources, it is not yet the final location for the bundle. It won't be picked up from here and placed to the APK.

copyDebugBundledJs Gradle task takes care of that and copies these files to multiple locations within android/app/build/intermediates directory:

.../intermediates/merged_assets/debug/out/index.android.bundle
.../intermediates/merged_assets/debug/mergeDebugAssets/out/index.android.bundle
.../intermediates/assets/debug/index.android.bundle

So why there are so many people trying to mimic this behavior with react-native bundle command? Because both bundleDebugJsAndAssets and copyDebugBundledJs are skipped by default when running assembleDebug task. The answer lies in the comments within android/app/build.gradle file:

The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets and bundleReleaseJsAndAssets). These basically call react-native bundle with the correct arguments during the Android build cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the bundle directly from the development server.

This behavior is determined by bundleInDebug configuration option, that is set to false by default because of the reasons mentioned above.

Conclusion

So where lies the solution?

Simply set bundleIn${buildType} to true. But not for the debug build type which is used in react-native run-android command. Define a new build type that produces unsigned APK:

andorid/app/build.gradle file:

...
project.ext.react = [
  ...
  bundleInStaging: true,
  devDisabledInStaging: true,
]
...
android {
  buildTypes {
    ... 
		staging {
      signingConfig signingConfigs.debug
      matchingFallbacks = ['debug']
    }
    ...

And simply run the assemble Gradle task from the android directory:

./gradlew assembleStaging

Now, whatever changes there will be in the future with the bundle and resource locations, you can have a restful sleep because React Native official Gradle tasks take care of that for you.

@abdullahizzuddiin
Copy link

Hi, thank you for your awesome article.

I just followed your instructions. It worked well when I used this conig

...
project.ext.react = [
  ...
  bundleInStaging: true,
  devDisabledInStaging: true,
]
...
android {
  buildTypes {
    ... 
		staging {
      signingConfig signingConfigs.debug
      matchingFallbacks = ['debug']
    }
}
...

But, I don't want to create new buildType. So, I tried with this config

...
project.ext.react = [
  ...
  bundleInDebug: true,
  devDisabledInDebug: true,
]
...
android {
  buildTypes {
        debug {
            signingConfig signingConfigs.debug
            matchingFallbacks = ['debug']
        }
        release {
            signingConfig signingConfigs.debug
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        }
}

I just replaced Staging with Debug on project.ext.react and I don't introduce new buildType on android.buildTypes. I only use default buildTypes created by Android/ReactNative.

The result is my debug app could run without development server. But, It still show development menu when I shake my device.

Do you have any idea?

@nikdo
Copy link
Author

nikdo commented Dec 17, 2020

Hi Abdullah,

The problem with the development menu I think comes from the usage of of the debug build type fallback. When you build the app, it's still in the debug configuration and because of that the developer menu is still enabled. I'm not sure how to overcome that, but I believe is certainly possible. You might find your answer in the react native codebase as I did with this article.

BTW you should not use debug for this IMO. By setting devDisabledInDebug to true, you are saying that development server and everything that comes with it won't be available in debug mode. Which you need for development. I believe there is nothing wrong with introducing a new build type.

Good luck!

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