Expect
It is expected for the release version of the Android app's build to run successfully when enabling minifyEnabled
and shrinkResources
per the Android documentation, Crashlytics, and Proguard.
Observe
The code for this issue may be found in the open-sourced GitHub repository for Coinverse.
The release version of the app crashes immediately when opened after being downloaded from Google Play.
Without
minifyEnabled
andshrinkResources
the app runs as expected in production, and the app size goes from ~4mb to ~10mb.The error log suggests it is an issue with Firebase compatibility and recommends this StackOverflow post. - What ProGuard configuration do I need for Firebase on Android?
There is a similar issue with ProGuard - transformClassesAndResourcesWithProguardForRelease FAILED.
Error
Summary
With Crashlytics enabled.
Crashlytics Core: Failed to execute task
Without Crashlytics enabled.
Failed to instantiate AndroidPlatform class using ProGuard
Details
2020-04-19 12:08:48.137 21704-21704/? E/app.coinverse: Unknown bits set in runtime_flags: 0x8000
2020-04-19 12:08:49.684 21704-21704/app.coinverse E/AndroidRuntime: FATAL EXCEPTION: main
Process: app.coinverse, PID: 21704
java.lang.RuntimeException: Unable to create application app.coinverse.App: java.lang.RuntimeException: Failed to instantiate AndroidPlatform class. Using ProGuard? See http://stackoverflow.com/questions/26273929/what-proguard-configuration-do-i-need-for-firebase-on-android
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6465)
at android.app.ActivityThread.access$1300(ActivityThread.java:219)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.RuntimeException: Failed to instantiate AndroidPlatform class. Using ProGuard? See http://stackoverflow.com/questions/26273929/what-proguard-configuration-do-i-need-for-firebase-on-android
at d.b.a.d.a.a(:69)
at d.b.a.a.a(:974)
at app.coinverse.j.a.<init>(:44)
at app.coinverse.j.b.a(:29)
at app.coinverse.j.b.get(:21)
at app.coinverse.j.b.get(:8)
at e.b.b.get(:47)
at app.coinverse.i.d.a(:107)
at app.coinverse.App.onCreate(:18)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1182)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6460)
at android.app.ActivityThread.access$1300(ActivityThread.java:219)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.NoSuchMethodException: d.b.a.c.a.<init> [class android.content.Context]
at java.lang.Class.getConstructor0(Class.java:2332)
at java.lang.Class.getConstructor(Class.java:1728)
at d.b.a.d.a.a(:62)
at d.b.a.a.a(:974)
at app.coinverse.j.a.<init>(:44)
at app.coinverse.j.b.a(:29)
at app.coinverse.j.b.get(:21)
at app.coinverse.j.b.get(:8)
at e.b.b.get(:47)
at app.coinverse.i.d.a(:107)
at app.coinverse.App.onCreate(:18)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1182)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6460)
at android.app.ActivityThread.access$1300(ActivityThread.java:219)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1859)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Implement
build.gradle (:app)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'androidx.navigation.safeargs'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.firebase-perf'
apply plugin: 'io.fabric'
apply plugin: 'de.mannodermaus.android-junit5'
android {
compileSdkVersion 29
defaultConfig {
applicationId "app.coinverse"
minSdkVersion 24
targetSdkVersion 29
versionCode 57
versionName "0.57"
kotlinOptions { jvmTarget = '1.8' }
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
sourceSets { androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) }
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro',
'proguard.cfg'
}
debug {
applicationIdSuffix ".staging"
debuggable true
}
open {
initWith debug
applicationIdSuffix ".open"
}
}
compileOptions { targetCompatibility JavaVersion.VERSION_1_8 }
dataBinding.enabled = true
testOptions { unitTests.includeAndroidResources = true }
}
dependencies {
def lifecycle_version = '2.2.0'
def lifecycle_test_version = '2.1.0'
def nav_version = '2.2.1'
def play_services_version = '17.0.0'
def exoplayer_version = '2.11.1'
def room_version = '2.2.5'
def glide_version = '4.11.0'
def mopub_version = '5.12.0'
def junit_version = '5.5.1'
def test_rules_core_version = '1.2.0'
def fragment_version = '1.2.4'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.dagger:dagger:2.27'
kapt 'com.google.dagger:dagger-compiler:2.27'
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1'
implementation "com.google.android.gms:play-services-auth:18.0.0"
implementation "com.google.android.gms:play-services-location:$play_services_version"
implementation 'com.firebase:firebase-client-android:2.5.2'
implementation 'com.google.firebase:firebase-analytics:17.3.0'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.google.firebase:firebase-perf:19.0.5'
implementation 'com.google.firebase:firebase-firestore-ktx:21.4.0'
implementation 'com.google.firebase:firebase-functions:19.0.2'
implementation 'com.google.firebase:firebase-auth:19.3.0'
implementation 'com.firebaseui:firebase-ui-firestore:4.2.0'
implementation 'com.firebaseui:firebase-ui-auth:4.2.1'
implementation 'com.google.firebase:firebase-storage:19.1.1'
implementation 'com.google.firebase:firebase-inappmessaging-display:19.0.4'
implementation 'com.google.firebase:firebase-config:19.1.3'
implementation 'com.jjoe64:graphview:4.2.2'
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
implementation files('libs/YouTubeAndroidPlayerApi.jar')
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "com.github.bumptech.glide:glide:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version"
implementation("com.mopub:mopub-sdk-native-static:$mopub_version") { transitive = true }
implementation("com.mopub:mopub-sdk-native-video:$mopub_version") { transitive = true }
implementation 'com.facebook.android:audience-network-sdk:5.8.0'
implementation 'com.mopub.mediation:facebookaudiencenetwork:5.8.0.0'
implementation 'com.flurry.android:ads:12.3.0'
implementation 'com.flurry.android:analytics:12.3.0'
implementation 'com.mopub.mediation:flurry:12.0.3.1'
// Testing
// Local Unit
testImplementation "org.junit.jupiter:junit-jupiter-api:$junit_version"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit_version"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junit_version"
testImplementation "io.mockk:mockk:1.9.3"
testImplementation 'org.assertj:assertj-core:3.13.2'
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
testImplementation "androidx.arch.core:core-testing:$lifecycle_test_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2"
// AndroidX - JVM
testImplementation "androidx.test:core-ktx:1.2.0"
testImplementation "androidx.test.ext:junit-ktx:1.1.1"
testImplementation "androidx.test:rules:$test_rules_core_version"
testImplementation "androidx.test:core:$test_rules_core_version"
}
build.gradle (android)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.71'
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://maven.google.com' }
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:perf-plugin:1.3.1'
classpath 'io.fabric.tools:gradle:1.31.2'
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"
classpath "de.mannodermaus.gradle.plugins:android-junit5:1.5.1.0"
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://maven.google.com/' }
maven { url "https://s3.amazonaws.com/moat-sdk-builds" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
App.kt
The release version of the app is crashing when attempting to initialize the FirebaseHelper.kt class.
class App : Application() {
val component = DaggerComponent.builder()
.utilsModule(UtilsModule(this))
.build()
override fun onCreate() {
super.onCreate()
// Here is where the crash occurs.
component.firebaseHelper()
MoPub.initializeSdk(this, SdkConfiguration.Builder(AD_UNIT_ID).build(), initSdkListener())
}
private fun initSdkListener() = SdkInitializationListener { /* MoPub SDK initialized.*/ }
}
FirebaseHelper.kt
@Singleton
class FirebaseHelper @Inject constructor(context: Context) {
private val LOG_TAG = FirebaseHelper::class.java.simpleName
init {
if (BuildConfig.BUILD_TYPE == open.name) {
var openSharedStatus = false
FirebaseApp.getApps(context).map { app ->
if (app.name.equals(open.name))
openSharedStatus = true
}
if (!openSharedStatus)
FirebaseApp.initializeApp(
context,
FirebaseOptions.Builder()
.setApplicationId(APP_ID_OPEN_SHARED)
.setApiKey(APP_API_KEY_OPEN_SHARED)
.setDatabaseUrl(DATABASE_URL_OPEN_SHARED)
.setProjectId(PROJECT_ID_OPEN_SHARED)
.setStorageBucket(STORAGE_BUCKET_OPEN_SHARED)
.build(),
open.name)
}
Firebase.setAndroidContext(context)
initializeRemoteConfig()
}
private fun initializeRemoteConfig() {
val firebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
firebaseRemoteConfig.setConfigSettingsAsync(FirebaseRemoteConfigSettings.Builder().build())
firebaseRemoteConfig.setDefaultsAsync(R.xml.remote_config_defaults)
val cacheExpiration = 3600L
try {
firebaseRemoteConfig.fetch(cacheExpiration)
firebaseRemoteConfig.fetchAndActivate()
} catch (exception: FirebaseRemoteConfigException) {
Crashlytics.log(Log.ERROR, LOG_TAG, "initializeRemoteConfig: ${exception.localizedMessage}")
}
}
}
Attempt Solutions
1. Remove Crashlytics.
A. Remove all instances of Crashlytics.log(...)
B. Remove Fabric plugin and Crashlytics library in build.gradle (:app)
// apply plugin: 'io.fabric'
...
// implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
C. Remove Fabric from build.gradle (android)
repositories {
...
// maven { url 'https://maven.fabric.io/public' }
}
dependencies {
...
// classpath 'io.fabric.tools:gradle:1.28.0'
}
2. Handle models' serialization/deserialization to/from Firestore's database with Proguard.
# Add this global rule
-keepattributes Signature
-keepclassmembers class app.coinverse.analytics.models.** {
*;
}
-keepclassmembers class app.coinverse.analytics.models.ContentAction.** {
*;
}
-keepclassmembers class app.coinverse.analytics.models.UserAction.** {
*;
}
-keepclassmembers class app.coinverse.analytics.models.UserActionCount.** {
*;
}
-keepclassmembers class app.coinverse.feed.models.** {
*;
}
-keepclassmembers class app.coinverse.feed.models.FeedViewState.** {
*;
}
-keepclassmembers class app.coinverse.feed.models.FeedViewState.Content.** {
*;
}
-keepclassmembers class app.coinverse.feed.models.FeedViewState.ContentToPlay.** {
*;
}
-keepclassmembers class app.coinverse.feed.models.FeedViewState.ContentPlayer.** {
*;
}
-keepclassmembers class app.coinverse.priceGraph.models.** {
*;
}
-keepclassmembers class app.coinverse.priceGraph.models.ExchangeOrderData.** {
*;
}
-keepclassmembers class app.coinverse.priceGraph.models.ExchangeOrdersDataPoints.** {
*;
}
-keepclassmembers class app.coinverse.priceGraph.models.MaximumPercentPriceDifference.** {
*;
}
-keepclassmembers class app.coinverse.priceGraph.models.Order.** {
*;
}
-keepclassmembers class app.coinverse.priceGraph.models.PercentDifference.** {
*;
}
-keepclassmembers class app.coinverse.priceGraph.models.PriceGraphData.** {
*;
}
-keepclassmembers class app.coinverse.priceGraph.models.PriceGraphXAndYConstraints.** {
*;
}
-keepclassmembers class app.coinverse.priceGraph.models.PricePair.** {
*;
}
-keepclassmembers class app.coinverse.user.models.** {
*;
}
-keepclassmembers class app.coinverse.user.models.User.** {
*;
}
-keepclassmembers class app.coinverse.utils.** {
*;
}
# If Enums are used in the database, use 'ADB Clear App Data and Restart', and then re-run.
-keepclassmembers enum * { *; }
B. Define models' no-argument constructors. This should not be required for models that already have default values defined in the data class
constructor. However, there is an error without explicitly defining the no-argument constructor, "users does not define no argument constructor".
C. Handle Enums by annotating enum names with @SerializedName
and adding the Proguard rule above to keep enumerations' members. Only one of these implementations should be required.
3. Add proguard.cfg rules for Firebase.
-keep class com.firebase.** { *; }
-keep class org.apache.** { *; }
-keepnames class com.fasterxml.jackson.** { *; }
-keepnames class javax.servlet.** { *; }
-keepnames class org.ietf.jgss.** { *; }
-dontwarn org.w3c.dom.**
-dontwarn org.joda.time.**
-dontwarn org.shaded.apache.**
-dontwarn org.ietf.jgss.**
# Only necessary if you downloaded the SDK jar directly instead of from maven.
-keep class com.shaded.fasterxml.jackson.** { *; }
4. Add pro-guard-rules-release.txt
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.txt'
testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-test.txt'