Android has many great build tools for compiling and configuring your app in the most ideal and organized way possible, but not without some unknown tricks that can throw you (or mostly me) off when trying to get there. The problems I ran into was when configuring gradle build types with proguard and my app which contained dynamicaly loaded classes. Arguably, not loading classes dynamicaly would have avoided this entire issue, but thats not the point, and for the future functionality of my app, was not an option.

The problem starts when trying to configure FireBase into my application. The recommended setup to seperate your development event logs from production development logs, is to have seperate applicationIds. This lead to configuring my gradle.build with the following (inside the android {} block) :

defaultConfig {

    applicationId "com.projectterris.myapplication"
    minSdkVersion 21
    targetSdkVersion 23
    versionCode 1
    versionName "1.0.0"
    multiDexEnabled true
}

buildTypes {  
    release {
        minifyEnabled true
        shrinkResources true
        proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}

debug {  
    applicationIdSuffix ".debug"
    debuggable true

    minifyEnabled true
    shrinkResources true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

Normaly, its not ideal to run proguard in your debug builds, as it causes your stack traces to be obfusicated, which causes issues when debugging. When I discovered this issue, I was testing my proguard setup and wanted to see the setup without having to create a release type.

After syncing and building I started getting these errors. Note my app was written in Kotlin, if your in Java and you have this issue, youll get roughly the same errors as Kotlin compiles down to Java bytecode anyway

Caused by: java.lang.ClassNotFoundException: Didn't find class "com.projectterris.myapplication.debug.datastore.dll.SQLiteHelper" on path: DexPathList[[zip file "/data/app/com.projectterris.myapplicaiton.debug-1/base.apk"],nativeLibraryDirectories=[/data/app/com.projectterris.myapplication.debug-1/lib/arm, /vendor/lib, /system/lib]]  

I eventualy discovered this was because of how my SQLiteHelper class was being dynamicaly loaded.

val classLoader = SQLiteHelper::class.java.classLoader  
val aClass = classLoader.loadClass("com.projectterris.myapplication.datastore.dll.SQLiteHelper")  
val ctor = aClass.getDeclaredConstructor(Context::class.java)  
val instance = ctor.newInstance(context) as SQLiteHelper  

Now you may be thinking "oh your fully qualified name does not contain 'debug' in it", but this causes an interesting problem. It seems that when you have different applicationIds configured in gradle, the package name to find all your compiled classes changes aswell. So for a release build the SQLiteHelper's canonical name is com.projectterris.myapplication.datastore.dll.SQLiteHelper and for debug it is com.projectterris.myapplication.debug.datastore.dll.SQliteHelper.

The fix for this can be implemented in a couple of different ways. For one, every android application has a BuildConfig object which contains a boolean variable that can be accessed as BuildConfig.DEBUG. This value will return true if your BuildType is debug. Consistency in this variable working though in Android Studio is argued alot on StackOverflow, so an alternative is preferrable. Another option is that you can set variables in gradle that can be accessed via the BuildConfig object.

Both of these options work in most cases, except for when proguard gets involved, and brings us to the next bug I encountered in my app. Proguard is basicaly the minifyer for Java/Kotlin, but in the process of doing so, also changes all the names of your classes and methods. This makes using a static string in the classLoader.loadClass(String) method not an option. Regardless of whether .debug is included in the canonical name or not, the actual class name or package will not be equivelent to its uncompiled canonical name. The solution I found to this was to use reflection again in determining the canonical name. This way, I always have the complete and proper canonical name, regardless of proguard or my buildtype. The code in the ended looked like this:

val classLoader = SQLiteHelper::class.java.classLoader  
val aClass = classLoader.loadClass(SQLiteHelper::class.java.canonicalName)  
val ctor = aClass.getDeclaredConstructor(Context::class.java)  
val instance = ctor.newInstance(context) as SQLiteHelper  

The next problem the appeared was from my constructor, which proguard decided at compile time was no longer needed and I ended up with errors like this:

Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context]  

This fortunatly was one of the easier fixes and is well described in the StackOverflow solution here : http://stackoverflow.com/questions/4447145/proguard-and-reflection-in-android

Basicaly, what I needed to do was tell proguard not to remove the constructor entry, thus allowing me to dynamicaly instantiate it. Adding the following rule to my proguard-rules.pro file, resolved this final issue:

-keepclassmembers class com.projectterris.myapplication.datastore.dll.SQLiteHelper{
 public <init>(android.content.Context);
}

Note that proguard rules can be used extensively and are quite powerful. The StackOverflow link above shows how you can also configure proguard to exempt any class that extends a common super class. This could be another fourth alternative to using the relective canonical name when loading dynamic classes with classLoader.loadClass(String). I haven't tested this, but telling proguard not to obfusicate the SQLiteHelper class could greatly simplify implementation, and reduce the reliance on reflection (which is known to be slow)

I ended up in this process also with AndroidManifest.xml permission errors not being shared between my .debug and normal applicationId apps, but later found out this was just intermediate files not being deleted properly. A clean and rebuild was able to resolve that issue. And so my project continues...