Using ConcurrentHashMap::newKeySet with Android coreLibraryDesugaring

Issue

I’m attempting to use the new desugaring features in AGP, however I am getting the following error when trying to use ConcurrentHashMap.newKeySet() in my app:

10-23 21:17:49.471 5023-5023/uk.org.mattford.scoutlink E/AndroidRuntime: FATAL EXCEPTION: main
    Process: uk.org.mattford.scoutlink, PID: 5023
    java.lang.NoSuchMethodError: No static method newKeySet()Lj$/util/concurrent/ConcurrentHashMap$KeySetView; in class Lj$/util/concurrent/ConcurrentHashMap; or its super classes (declaration of 'j$.util.concurrent.ConcurrentHashMap' appears in /data/app/uk.org.mattford.scoutlink-1/base.apk:classes3.dex)
        at org.pircbotx.hooks.managers.ThreadedListenerManager.<init>(ThreadedListenerManager.java:49)
        at org.pircbotx.Configuration$Builder.getListenerManager(Configuration.java:884)
        at org.pircbotx.Configuration$Builder.addListener(Configuration.java:726)
        at uk.org.mattford.scoutlink.irc.IRCService.connect(IRCService.java:143)
        at uk.org.mattford.scoutlink.irc.IRCService.onStartCommand(IRCService.java:67)
        at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2864)
        at android.app.ActivityThread.access$2100(ActivityThread.java:144)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1376)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5221)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

I have the following in my top level build.gradle

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.0'
        classpath 'com.google.gms:google-services:4.3.4'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        maven {
            url 'https://oss.sonatype.org/content/repositories/snapshots'
        }
    }
}

and in my module build-gradle:

apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'

android {
    compileSdkVersion 29
    buildToolsVersion '29.0.3'
    defaultConfig {
        applicationId "uk.org.mattford.scoutlink"
        minSdkVersion 16
        targetSdkVersion 29
        multiDexEnabled true
    }
    buildTypes {
        release {}
    }
    packagingOptions {
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/license.txt'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/ASL2.0'
    }
    compileOptions {
        coreLibraryDesugaringEnabled true
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    productFlavors {
    }
    buildFeatures {
        viewBinding = true
    }
}

dependencies {
    implementation 'org.slf4j:slf4j-android:1.7.30'
    implementation 'org.pircbotx:pircbotx:2.3-SNAPSHOT'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.firebase:firebase-analytics:17.6.0'
    implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
    implementation 'com.google.android.material:material:1.2.1'

    implementation "androidx.room:room-runtime:2.2.5"
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
    annotationProcessor "androidx.room:room-compiler:2.2.5"

    implementation 'androidx.multidex:multidex:2.0.1'
    coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
}

Looking at the APK analyser. I can see that j$.util.concurrent.ConcurrentHashMap is defined in classes3.dex but does not include the newKeySet method. However j$.util.concurrent.ConcurrentHashMap is also defined in classes4.dex and does have this method!

The ConcurrentHashMap.newKeySet call I am trying to work around is in the pircbotx dependency. However I have put a call to this method in my Application’s onCreate method and this also crashes the app.

Thanks in advance for any help. I’ve been at this for hours now to no avail.

Solution

This turned out to be a "bug" in D8, it turns out that although the following 3 methods are listed in the docs (https://developer.android.com/studio/write/java8-support-table) as supported, they are not actually currently supported.

This has been raised a feature request but according to the D8 team it is unlikely they will be looked at this quarter. (full discussion of the issue here: https://issuetracker.google.com/171666278)

public ConcurrentHashMap.KeySetView keySet(Object mappedValue)
public static ConcurrentHashMap.KeySetView newKeySet()
public static ConcurrentHashMap.KeySetView newKeySet(int initialCapacity)

Answered By – Matt

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published