Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android java.lang.NoClassDefFoundError: R$string

whenever i run a unit test, i get this error when it tries to retrieve a string value from a custom config.xml file.

Background:

The project itself is a library apk project that uses and references another library APK project.

The error occurs when the project itself tries to initiate a new object that is a subclass of a super class contained on the referenced library apk project.

The Issue explained more below

The specific line of code that fails with the error is a static variable defined below:

  protected static final String ANDROID_CONFIG_ID =  LibraryApplication.getApplication().getString(R.string.api_checkout_config_id_override);

it fails with the following error:

java.lang.NoClassDefFoundError: com/jon/commonlib/R$string

commonLib is the referenced library apk if you are wondering.

Here is my unit test

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, manifest=Config.NONE)
public class TestSearchShows {

    @Test
    public void testSearchJsonFile(){
        EventsTestHelper testHelper = new EventsTestHelper(RuntimeEnvironment.application);

        try {
            ShowsList showList = testHelper.getShowList(new SearchEvent());

            if(showList.getShows().size() < 0){
                Assert.fail("show list is empty");
            }

        } catch (IOException e) {
            e.printStackTrace();
            Assert.fail(e.toString());
        }
    }


}

The EventsTestHelper is the sub class for a super class called NetworkHelper which looks like this:

public abstract class NetworkHelper<T, P, S> implements NetworkConstants {

protected static final String ANDROID_CONFIG_ID = LibraryApplication.getApplication().getString(R.string.api_checkout_config_id_override);

//other stuff ....

}

I use robolectric version 3.0 the latest for runing ,my unit tests.

If i was to run the code live and call and initiate the sub class, it works perfectly fine, no crashes

edit: Here is snippets of my build gradle file below

apply plugin: 'android-sdk-manager'
apply plugin: 'com.android.library'
apply plugin: 'crashlytics'


buildscript {
    repositories {
        jcenter()
        mavenCentral()
        maven { url 'http://download.crashlytics.com/maven' }
        maven { url 'http://www.testfairy.com/maven' }

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.1.0'
        classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.+'
        classpath 'com.crashlytics.tools.gradle:crashlytics-gradle:1.+'
        classpath 'com.testfairy.plugins.gradle:testfairy:1.+'

    }
}

repositories {
    mavenCentral()
    maven { url 'http://download.crashlytics.com/maven' }
    maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
    flatDir {
        dirs 'libs'
    }
}

android {
    compileSdkVersion 19
    buildToolsVersion "22.0.1"
    def package_namespace = "com.jonney.moduleApp"

   defaultConfig {
        minSdkVersion 14
        testApplicationId "com.jonney.moduleApp"
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
 productFlavors {
        //testing {
        //}
        local {
        }
        mock {
        }
        qa {
        }
        //qa4 {
        //}
        production {
        }
    }

    sourceSets {



        local {
            res.srcDir 'build-config/local/res'
        }
        testing {
            res.srcDir 'build-config/testing/res'
        }
        mock {
            res.srcDir 'build-config/mock/res'
        }
        qa {
            res.srcDir 'build-config/qa/res'
        }
        qa4 {
            res.srcDir 'build-config/qa4/res'
        }
        staging {
            res.srcDir 'build-config/staging/res'
            test.java.srcDirs += 'src/main/java'
        }
        production {
            res.srcDir 'build-config/production/res'
            test.java.srcDirs += 'src/main/java'
            test.java.srcDirs += "build/generated/source/r/production"
            test.java.srcDirs += "build/generated/source/buildConfig/production"
        }

    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:19.1.+'
    compile 'com.google.code.gson:gson:2.3'
    testCompile('org.robolectric:robolectric:3.0') {
        exclude group: 'commons-logging', module: 'commons-logging'
        exclude group: 'org.apache.httpcomponents', module: 'httpclient'
    }
    compile 'com.fasterxml.jackson:jackson-parent:2.5'
    compile 'com.squareup:otto:1.3.6'
    compile 'com.jakewharton:butterknife:6.1.0'
    compile 'com.sothree.slidinguppanel:library:3.0.0'
    compile 'com.crashlytics.android:crashlytics:1.+'
    compile 'com.mcxiaoke.volley:library-aar:1.0.0'
    compile "joda-time:joda-time:2.4"
    testCompile('junit:junit:4.12') {
        exclude module: 'hamcrest'
        exclude module: 'hamcrest-core'
    }
    testCompile 'org.hamcrest:hamcrest-all:1.3'
    compile 'com.sothree.slidinguppanel:library:3.0.0'
    compile 'com.squareup:otto:1.3.6'
    compile 'com.squareup.okhttp:okhttp:2.3.0'
    testCompile 'org.apache.maven:maven-ant-tasks:2.1.3'

    compile 'com.google.android.gms:play-services:7.0.0'

    compile 'com.android.support:multidex:1.0.0'

    compile(name: 'commonLib 1.0 1', ext: 'aar')
    testCompile(name: 'commonLib-1.0 1', ext: 'aar')
}

edit: i have also tried to manually create a task that copies the r class for each dependency.

    afterEvaluate { project ->
        android.libraryVariants.each { variant ->
            // workaround for missing R class for aar dependencies
            def copyTaskName = "copy${variant.name.capitalize()}AppCompat"
            def copyTaskNameTwo = "copy${variant.name.capitalize()}commonlib"
            task(copyTaskName, type:Copy) {
                dependsOn "process${variant.name.capitalize()}Resources"
                from { "build/generated/source/r/${variant.dirName}/$package_namespace_path" }
                into { "build/generated/source/r/${variant.dirName}/com/jon/commonlib" }
//                into { "src/test/java/android/support/v7/appcompat" }
                include 'R.java'
                filter { line -> line.contains("package ${package_namespace};") ? 'package android.support.v7.appcompat;' : line }
                outputs.upToDateWhen { false }
            }
            task(copyTaskNameTwo, type:Copy) {
                dependsOn "process${variant.name.capitalize()}Resources"
                from { "build/generated/source/r/${variant.dirName}/$package_namespace_path" }
                into { "build/generated/source/r/${variant.dirName}/android/support/v7/appcompat" }
//                into { "src/test/java/android/support/v7/appcompat" }
                include 'R.java'
                filter { line -> line.contains("package ${package_namespace};") ? 'package com.jon.commonlib;' : line }
                outputs.upToDateWhen { false }
            }
            System.out.println("adding ${copyTaskName} build/generated/source/r/${variant.dirName}/$package_namespace_path ")
            println("basename =  ${variant.baseName}")
            println("directory name =  ${variant.dirName}")
            tasks.getByName("compile${variant.name.capitalize()}UnitTestJava") dependsOn copyTaskName
        }
    }

Kind regards

jonnney

like image 323
Jonathan Avatar asked Nov 27 '22 05:11

Jonathan


1 Answers

The project itself is a library apk project that uses and references another library APK project.

For this type of projects exist an already known issue https://github.com/robolectric/robolectric/issues/1796 but you can workaround it.

The base issue is the android/gradle behaviour for library projects which is different compared to application projects. It just ignores to generate R classes from aar dependencies.

To workaround you can provide your own R class which already contains all dependencies R values. Here an example for the appcompat dependency

afterEvaluate { project ->
  android.libraryVariants.each { variant ->
    // one line for each aar dependency
    tasks.getByName("assemble${variant.name.capitalize()}").dependsOn copyAppcompat
  }
}

// copy it for each aar dependency and adjust it to your needs
task copyAppcompat(type: Copy) {

  // replace the base package with yours (all after /r/debug/) which contains your R.class
  from 'build/generated/source/r/debug/com/example/core'.replace("/", File.separator)

  // replace the library packages with yours (all after /test/java/) with your aar dependency base package
  into 'src/test/java/android/support/v7/appcompat'.replace("/", File.separator)

  // also replace the package declaration or you will get compile errors
  filter { line -> line.contains('package com.example.core;') ? 'package android.support.v7.appcompat;' : line }
  include 'R.java'
}

An example project with your setup can be found at https://github.com/nenick/AndroidStudioAndRobolectric/tree/library-with-aar

like image 127
nenick Avatar answered Jan 13 '23 22:01

nenick