Search code examples
androidandroid-studiogradlebuildswagger

DexArchiveMergerException only on release


I developed an app that uses a swagger-generated Java client. The client is in a project called "api", while the app is in a project called "app".

When I build the app via Build/Make Project everything works fine. Also, when I try to run the app on an emulated or physical device Run/Run 'app'. Running with debugger works as well. Even when I build the project via Buid/Generate Signed Bundle/APK and choose the debug option, it works.

Now. The build fails when I try to create a signed release build. The following messages show:

Caused by: java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: [...]\app\build\intermediates\transforms\dexBuilder\release\54, [...]\app\build\intermediates\transforms\externalLibsDexMerger\release\0, [...]\app\build\intermediates\transforms\dexBuilder\release\52.jar, [...]\app\build\intermediates\transforms\dexBuilder\release\53.jar


Caused by: com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: [...]\app\build\intermediates\transforms\dexBuilder\release\54, [...]\app\build\intermediates\transforms\externalLibsDexMerger\release\0, [...]\app\build\intermediates\transforms\dexBuilder\release\52.jar, [...]\app\build\intermediates\transforms\dexBuilder\release\53.jar


Caused by: com.android.tools.r8.CompilationFailedException: Compilation failed to complete


Caused by: com.android.tools.r8.utils.AbortException: Error: Program type already present: io.swagger.client.ApiCallback

I am pretty new to developing with Android Studio and Gradle. I tried some solutions on Stack Overflow that already suggested to add some libraries but so far none of them worked with my problem.

To me, its especially suspicious that the last error message points to io.swagger.client.ApiCallback.

Could it have anything to do with the fact that both settings.gradle for "api" and "app" have the same content? Both look like this: rootProject.name = "swagger-java-client". This is the only line in the file, but as far as I know, the settings.gradle for "app" already hat that content. I cant remember changing it so its odd to me that it shows "swagger-java-client". Is that normal?

Update 1

ProGuard has been mentioned in the comments to might be a problem. This is the only occurrence I could find, referencing it. In my build.gradle for the "app"-project, there is this part:

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

However, removing it did not change anything.

Update 2

When searching for "io.swagger" AS only finds this:

only one match found

When searhing for "io.swagger.client.ApiCallback" AS only finds this:

only one match found

Update 3

As suggested, I tried adding

android {
    defaultConfig {
        multiDexEnabled true
    }
}

and adding android.enableD8 = false but that didn't help either.

Here are my build.gradle-Files (the first ones that caused the problems, without the suggested corrections I've tried so far.)

build.gradle (Module: api) (generated by Swagger):

apply plugin: 'idea'
apply plugin: 'eclipse'

group = 'io.swagger'
version = '1.0.0'

buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.0'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
    }
}

repositories {
    jcenter()
}


if(hasProperty('target') && target == 'android') {

    apply plugin: 'com.android.library'
    apply plugin: 'com.github.dcendents.android-maven'

    android {
        compileSdkVersion 25
        buildToolsVersion '25.0.2'
        defaultConfig {
            minSdkVersion 14
            targetSdkVersion 25
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_7
            targetCompatibility JavaVersion.VERSION_1_7
        }

        // Rename the aar correctly
        libraryVariants.all { variant ->
            variant.outputs.each { output ->
                def outputFile = output.outputFile
                if (outputFile != null && outputFile.name.endsWith('.aar')) {
                    def fileName = "${project.name}-${variant.baseName}-${version}.aar"
                    output.outputFile = new File(outputFile.parent, fileName)
                }
            }
        }

        dependencies {
            provided 'javax.annotation:jsr250-api:1.0'
        }
    }

    afterEvaluate {
        android.libraryVariants.all { variant ->
            def task = project.tasks.create "jar${variant.name.capitalize()}", Jar
            task.description = "Create jar artifact for ${variant.name}"
            task.dependsOn variant.javaCompile
            task.from variant.javaCompile.destinationDir
            task.destinationDir = project.file("${project.buildDir}/outputs/jar")
            task.archiveName = "${project.name}-${variant.baseName}-${version}.jar"
            artifacts.add('archives', task);
        }
    }

    task sourcesJar(type: Jar) {
        from android.sourceSets.main.java.srcDirs
        classifier = 'sources'
    }

    artifacts {
        archives sourcesJar
    }

} else {

    apply plugin: 'java'
    apply plugin: 'maven'

    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7

    install {
        repositories.mavenInstaller {
            pom.artifactId = 'swagger-java-client'
        }
    }

    task execute(type:JavaExec) {
       main = System.getProperty('mainClass')
       classpath = sourceSets.main.runtimeClasspath
    }
}

dependencies {
    compile 'io.swagger:swagger-annotations:1.5.21'
    compile 'com.squareup.okhttp:okhttp:2.7.5'
    compile 'com.squareup.okhttp:logging-interceptor:2.7.5'
    compile 'com.google.code.gson:gson:2.8.2'
    compile 'org.threeten:threetenbp:1.3.5'
    testCompile 'junit:junit:4.12'
}

build.gradle (Module: app):

buildscript {
    repositories {
        maven { url 'https://maven.fabric.io/public' }
    }

    dependencies {
        classpath 'io.fabric.tools:gradle:1.+'
    }
}
apply plugin: 'com.android.application'
apply plugin: 'io.fabric'

repositories {
    maven { url 'https://maven.fabric.io/public' }
}

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "projectName"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "0.1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support:design:28.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation fileTree(dir: '../api/build/libs', include: ['*.jar'])
    implementation 'com.google.code.gson:gson:2.8.2'
    implementation 'com.squareup.okhttp:okhttp:2.7.5'
    api project(path: ':api')
    implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
    implementation 'org.jetbrains:annotations-java5:15.0'
}

Solution

  • I've found the answer to my question:

    What I did was to create the swagger client, move the created folder into my project, adjust the settings.gradle file and the build.gradle file accordingly.


    What went wrong

    The way I configured things, Gradle would build the ´api´ project and then the app project. For some reason, when I configured things as release, the app project referenced the code of the api project, as well as the resulting .jar file. That's where the Program type already present came from.


    How I fixed it

    • Exclude the api project from the app project
    • Build api separately
    • Take the resulting .jarfile and put it in a folder inside your app folder called libs
    • Put this line in your Gradle dependencies for your build.gradle (Module: app): implementation fileTree(dir: 'libs', include: ['*.jar'])

    This included the finished .jarfile in the appproject and now I can build release. This should be done regardless, because the swagger client shouldn't be edited anyway, as it is generated code. So even just to not always build that part of the project, this should be done.

    However, I still don't fully understand why there is this difference between the release and the debugbuild. I am still seeking insight to that.


    EDIT: ADDITIONAL INFORMATION

    I just found out: if, for some reason, I would have wanted to keep the Module: api in my project I could have done so by removing the two dependencies in my Module: appthat start with implementation fileTree.

    The api-project is already included by the dependency api project(path: ':api'). The filetree-lines were the ones causing the problem here, since they were redundant.