3

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.

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.

A. Add proguard.cfg rules

# 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'
Adam Hurwitz
  • 6,459
  • 4
  • 48
  • 92

1 Answers1

0

I've had a similar issue and fully migrating from Fabric to Firebase Crashlytics fixed the issue for me: https://firebase.google.com/docs/crashlytics/get-started-new-sdk?platform=android&authuser=0

Hope this helps!

Tudor S.
  • 688
  • 11
  • 25
  • 1
    That is promising to hear @Tudor S! I will report back once I have implemented the migration to the Crashlytics beta to see if it resolves the issue. – Adam Hurwitz Apr 29 '20 at 05:18
  • I followed the documentation to install _[Crashlytics](https://firebase.google.com/docs/crashlytics/get-started?authuser=0&platform=android)_ and confirmed it is working as expected in the Firebase console. Unfortunately, the Proguard issue above persists. The next step is to create a basic sample app to reproduce the issue in order to file an issue with the Android team. – Adam Hurwitz Aug 07 '20 at 22:55