Skip to content

Instantly share code, notes, and snippets.

@tylergaw
Last active November 30, 2018 13:59
Show Gist options
  • Save tylergaw/9d49f47061d71f1fa7b3a6b10c8769bc to your computer and use it in GitHub Desktop.
Save tylergaw/9d49f47061d71f1fa7b3a6b10c8769bc to your computer and use it in GitHub Desktop.

Notes on a Gradle Bug Fix in a React Native Android project

Posted 2018-11-30

The problem

After adding native Android dependencies the app was crashing right after startup. This was happening on both Android emulators and live devices. Using abd logcat I was able to see the error causing the crash:

java.lang.NoClassDefFoundError: Failed resolution of: Lcom/google/android/gms/common/api/Api$zz

This was hard to find a fix for, it took me–cumulatively–about two work days to get it fixed. 😬 Google searches for the error return quite a few other folks with the same or similar issue. Using those examples I was able to piece something together that worked for our situation.

react-native-info
React Native Environment Info:
    System:
      OS: macOS 10.14
      CPU: (8) x64 Intel(R) Core(TM) i7-3615QM CPU @ 2.30GHz
      Memory: 29.81 MB / 8.00 GB
      Shell: 3.2.57 - /bin/bash
    Binaries:
      Node: 8.12.0 - ~/.nvm/v8.12.0/bin/node
      Yarn: 1.12.3 - /usr/local/bin/yarn
      npm: 6.4.1 - ~/.nvm/v8.12.0/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.0, macOS 10.14, tvOS 12.0, watchOS 5.0
      Android SDK:
        API Levels: 27, 28
        Build Tools: 27.0.3, 28.0.3
    IDEs:
      Android Studio: 3.2 AI-181.5540.7.32.5056338
      Xcode: 10.0/10A255 - /usr/bin/xcodebuild
    npmPackages:
      react: 16.6.1 => 16.6.1
      react-native: 0.57.5 => 0.57.5
    npmGlobalPackages:
      react-native-cli: 2.0.1
      react-native: 0.57.5

The cause

We included https://github.com/zo0r/react-native-push-notification in the project for push notification support. Bringing that into the project via settings.gradle and app/build.gradle triggered the crash. Nothing beyond including it was needed to cause issues.

After much Googling it seemed that react-native-push-notification's version of play-services-gcm and firebase-messaging was causing conflicts with other project dependenies, leading to the crash. I don't fully understand how the gradle dependencies work and why this is an issue, but it was.

The fix

In tolu360/react-native-google-places#142 (comment) the author outlines a fix for the same issue relating to a different module. The idea there is to exclude transitive module dependencies https://docs.gradle.org/current/userguide/managing_transitive_dependencies.html#sec:excluding_transitive_module_dependencies Again, even after reading that I'm still hazy on how it all fits together, but here we are.

Before making a similar change to our gradle setup, I wanted to understand more about what the push notification module was doing. Specifically what version of the conflicting deps it required. In https://github.com/zo0r/react-native-push-notification/blob/master/android/build.gradle#L55 we can see that it's requesting versions of play-services-gcm and firebase-messaging based on if we have global version settings. In our case we have googlePlayServicesVersion = "11.0.0" in our global project build.gradle, but nothing for firebaseVersion so it would use whatever the default is there.

OK, so to exclude those troublesome dependencies we change our app/build.gradle from:

...
implementation project(':react-native-push-notification')
...

to:

implementation (project(':react-native-push-notification')) {
    exclude group: 'com.google.android.gms', module: 'play-services-gcm'
    exclude group: 'com.google.firebase', module: 'firebase-messaging'
}

implementation "com.google.android.gms:play-services-gcm:16.0.0"
implementation "com.google.firebase:firebase-messaging:17.3.4"
...

Included in this gist are full copies of our build.gradle and app/build.gradle files so you can see the changes in context.

So–as far as I can tell–what we're saying there is; "hey gradle, don't use react-native-push-notification's versions of play-services-gcm and firebase-messaging, instead use these specific versions here." And again run react-native run-android and open the app. Still crashing with a similar error. The error showed me that we had another third party module with conflicting dependencies. This time it was https://github.com/Agontuk/react-native-geolocation-service/blob/master/android/build.gradle#L29

The fix for that is the same process, update app/build.gradle from:

...
implementation project(':react-native-geolocation-service') 
...

to:

...
implementation (project(':react-native-geolocation-service')) {
    exclude group: 'com.google.android.gms', module: 'play-services-location'
} 

implementation "com.google.android.gms:play-services-location:16.0.0"
...

and again react-native run-android and open the app. No more crashing.

apply plugin: "com.android.application"
import com.android.build.OutputFile
project.ext.react = [
entryFile: "index.js"
]
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "com.streetcred.mapnyc"
minSdkVersion 16
targetSdkVersion 26
versionCode 17
versionName "2.0.0-beta"
ndk {
abiFilters "armeabi-v7a", "x86"
}
multiDexEnabled true
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86"
}
}
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
}
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
signingConfig signingConfigs.release
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
def versionCodes = ["armeabi-v7a":1, "x86":2]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
lintOptions {
abortOnError false
}
}
dependencies {
implementation project(':appcenter')
implementation project(':appcenter-analytics')
implementation project(':appcenter-crashes')
implementation project(':appcenter-push')
implementation project(':react-native-google-analytics-bridge')
implementation project(':react-native-code-push')
implementation project(':react-native-vector-icons')
implementation project(':react-native-splash-screen')
implementation project(':react-native-image-picker')
implementation project(':react-native-device-info')
implementation project(':react-native-android-open-settings')
implementation project(':@mapbox_react-native-mapbox-gl')
implementation (project(':react-native-push-notification')) {
exclude group: 'com.google.android.gms', module: 'play-services-gcm'
exclude group: 'com.google.firebase', module: 'firebase-messaging'
}
implementation (project(':react-native-geolocation-service')) {
exclude group: 'com.google.android.gms', module: 'play-services-location'
}
/**
* See https://github.com/mapbox/react-native-mapbox-gl/issues/1139
* for reasoning behind extra configuration here.
*/
implementation (project(":@mapbox_react-native-mapbox-gl")) {
implementation ('com.squareup.okhttp3:okhttp:3.6.0') {
force = true
}
}
implementation "com.google.android.gms:play-services-gcm:16.0.0"
implementation "com.google.android.gms:play-services-location:16.0.0"
implementation "com.google.firebase:firebase-messaging:17.3.4"
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:24.0.0"
implementation "com.facebook.react:react-native:+" // From node_modules
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = "27.0.3"
minSdkVersion = 16
compileSdkVersion = 27
targetSdkVersion = 26
supportLibVersion = "27.1.1"
googlePlayServicesVersion = "11.0.0"
}
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenLocal()
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
maven { url "https://jitpack.io" }
maven { url "https://maven.google.com" }
google()
}
}
subprojects { subproject ->
afterEvaluate{
if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '4.4'
distributionUrl = distributionUrl.replace("bin", "all")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment