Redeclaration error with same classname in different Source Sets - android

Similar to flavor variants, I was expecting the ability to have source set variants for the same classname. For example,
androidTest directory
class RobotTest(val name: String = "androidTest")
main directory
class RobotTest(val name: String = "main")
test directory
class RobotTest(val name: String = "test")
Android Studio is okay when I have the same classname in just 2 of the sources:
main and test
main and androidTest
However, once I add the same class to all three, Android Studio shows an Analysis error in test and androidTest with the message:
Redeclaration: RobotTest
I ran the code in a unit test, instrumented test, and in the app and everything compiled fine (no build errors) and when logging RobotTest().name, I correctly get the 3 different values for each class context.
Does anyone have any idea how to fix this or if this isn't allowed?

When you have created a product flavor you should not place your varianted class into main source set.
Keep only common for all variants files into main
For example, you defined:
flavorDimensions("api")
productFlavors {
create("real") {
dimension = "api"
}
create("mock") {
dimension = "api"
}
}
Since that, you have exactly TWO build variants: real OR mock.
So, you have to have exactly TWO versions of your class (if it should be different):
/src/real/kotlin/com/example/SomeApi.kt
/src/main/kotlin/com/example/ (no file here)
/src/mock/kotlin/com/example/SomeApi.kt
UPD:
All above is correct only for sources! If you create 4 flavors, you must copy your Java/Kotlin class into 4 folders, you don't have a 'default' one.
For resources, the logic is different.
Gradle, at first, takes the resource file (some XML file) from the default folder 'main' and then replaces it with the overridden one from the flavor folder if the file exists there. If you create 4 flavors, you can keep a default in 'main' and you can override it only once.

Related

BuildConfigField R.style.AKTheme is not accessible in androidTest BuildConfig.java class

My project has different build types i.e. debug, beta and production and also have different product flavors i.e. QA and Integration. I have defined
a
buildConfigField 'int', 'APP_THEME', 'R.style.AKTheme'
in the productFlavors to have a separate theme for each flavor. The generated BuildConfig.java for app source set have the APP_THEME field and it is working as expected.
Recently I have started writing instrumentation tests for my app. When I try to run these tests Android studio gives me the error that can not resolve AKTheme i.e.
final int APP_THEME = R.style.AKTheme in the generated BuildConfig.java for the test source set.
It seems that R.style.AKTheme is not accessible to the generated BuildConfig.java file (test source set). I searched over internet but didn't find any help.
R.style.AKTheme is a reference, not a value, while in BuildConfig you can only use values.
There are couple of ways to achieve what you want:
Use the String name of the style in BuildConfig:
buildConfigField 'String', 'APP_THEME', '"AKTheme"'
and then in code to get the style res id:
int style = context.getResources().getIdentifier(BuildConfig.APP_THEME, "style", context.getPackageName());
Now you can use style.
You can use different source-sets.
If you are using different buildtypes, you can create a directory for that build type, and put any different resources specially for that build type in that directory. The directory should be created in the same directory as main sources directory, and named exactly the same as the buildType. Details: https://developer.android.com/studio/build/build-variants
I had quite the same issue. I had this in my build.gradle to get the right ssl certificate depending on the env. :
buildConfigField 'int', 'SSL_CERTIFICAT_RAWRES_', 'R.raw.devcert'
This was working to build and run the project, but I faced an issue when I wanted to run the task : "compileDebugAndroidTestJavaWithJavac" (used for sonarqube in my case). The compiler didn't found the resource file R when he automatically generates the buildConfig file.
The solution was to put a String to identify my certificate file "devcert" in the build.gradle instead of using directly the resInt "R.raw" :
buildConfigField 'String', 'SSL_CERTIFICAT_RAWRES_STRING', '"devcert"'
and then in my code, I get the raw file certificate like this :
final int raw = context.getResources().getIdentifier(BuildConfig.SSL_CERTIFICAT_RAWRES_STRING, "raw", context.getPackageName());
That way, the generated buildConfig found correctly the String identifier to retrieve the wanted raw file using the code above.
I found my answer here: https://developer.android.com/studio/test#create_instrumented_test_for_a_build_variant
For my case my test project and my main project is referencing a different package name, e.g. at the top of BuildConfig.java one referencing to package 'com.xxx.xxx' while one is referencing to 'com.xxx.xxx.test'
adding the line
testApplicationId = "com.xxx.xxx"
in defaultConfig in app level build.gradle file solves the issue for me

Robolectric not finding resources for flavors with different app IDs

In Android Studio, I am writing unit tests with Robolectric for multiple flavors, and it seems that I can only access the resources from the tests of the flavor with the default application ID.
My test directory structure is:
-src
...
- test (these tests will run on all flavors)
- java
- com.example.main
- UnitTest.java
- testDefault (these tests will only run on flavor "main")
- java
- com.example.main
- DefaultUnitTest.java
- testOtherFlavor (these tests will only run on flavor "otherFlavor")
- java
- com.example.otherflavor
- OtherFlavorUnitTest.java
- main
...
- res
- values
- strings.xml
- otherflavor
- res
- values
- strings.xml
In my build.gradle I have
productFlavors {
default{}
otherFlavor {
applicationId "com.example.otherflavor"
versionNameSuffix "-otherFlavor"
}
}
My test is the following:
package com.example.otherflavor;
import android.context.Context;
import com.example.main.R;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
#RunWith(RobolectricTestRunner.class)
#Config(packageName = com.example.otherflavor)
public class OtherFlavorUnitTest {
private Context contextMock;
#Test
public void test() {
contextMock = RuntimeEnvironment.application.getApplicationContext();
System.out.println(contextMock.getString(R.string.someString));
}
}
someString is located in both strings.xml files with different values, so that when running the flavor "otherflavor" the value of its strings.xml overrides that of the default flavor. The problem is that when I run this code in the DefaultUnitTest.java (with the package names accordingly changed) the test passes, however when I run this code in the OtherFlavorUnitTest.java I get:
android.content.res.Resources$NotFoundException: Unable to find resource ID #0x7f0e0059 in packages [com.example.otherflavor, android]
However if I remove "applicationId "com.example.otherflavor"" from the build.gradle the tests pass (and the resource retrieved by getString() is indeed the resource of otherFlavor not of the default flavor).
Does someone know why Robolectric cannot find resources when the flavor has a different App ID?
The short answer is that test must be flexible enough to run its tests no matter what the applicationId is set to. When done correctly, you select the flavor from the Build Variant window. Then the tests run against the currently selected flavor. Alternatively, you can run ./gradlew testFlavor or ./gradlew testDefault from the command line.
You can create a class to be a provider of the resource in each flavor test directory so each file will have a import matching the aplicationId like com.example.myapp.flavorOne.test.R or flavorTwo.test.R
and then from test class that would be on androidTest(the main test directory) you can get the resource from the resource provider you created matching the build variant (flavor) you are

Common code for different android flavors

I am building 4 different flavors of my Android app.
I have a class Customization.java that is the same for 3 of them and different for 1.
Since I cannot put the same class both in the main folder and in the flavor folder, I now have to maintain 3 copies of the exact same class for those 3 flavors.
Is there any way that I could do with keeping just two versions of this class?
Things I have considered so far:
I looked at flavor dimensions, but turns out they are not applicable in this case.
Keeping just one file in one of the flavors and copying it through my build script.
I am wondering if there is something cleaner out of the box.
I would like to convert CommonsWare's comment to an answer. I'll then explain how the final directory setup should look like.
Well, you can override resources in flavors. So, have the common one
in main/res/layout/ and the flavor-specific one in
yourFlavorHere/res/layout/.
So, if the Customization activity's layout file is called activity_customization.xml, you'll leave its common copy shared among the three flavors under src/main/res/layout directory and place the modified layout xml to be used by, say flavorFour, under its corresponding source set directory src/flavorFour/res/layout.
The way this works is that since flavor one to three (unlike flavor four) haven't provided their own versions of activity_customization.xml, they'll inherit the one coming from the main source set.
It's the activity Java class that gets tricky. Another possibility for
that is to configure the flavors with the same activity implementation
to pull from two source directories: a flavor-specific one and a
common one with the common class implementation.
Unlike resources, Java code files are not merged or overridden. So, you can't have Java files with the same fully qualified class name under main as well as in any of your flavor source sets. If you do, you'll receive a duplicate class error.
To resolve this issue, the simplest solution is to move Customization activity out of the main and into each flavor source set. This works because the flavor directories are mutually exclusive (with each other, not with main) hence avoiding the conflict.
But this means three out of the four flavors have a duplicate copy of the activity - a maintenance nightmare - just because one of the flavors required some changes to it. To resolve this issue we can introduce another source directory that keeps just the common code files shared between the three flavors.
So, the build.gradle script would look something like
android {
...
productFlavors {
flavorOne {
...
}
flavorTwo {
...
}
flavorThree {
...
}
flavorFour {
...
}
}
sourceSets {
flavorOne.java.srcDir 'src/common/java'
flavorTwo.java.srcDir 'src/common/java'
flavorThree.java.srcDir 'src/common/java'
}
}
Notice the use of java.srcDir (and not srcDirs) which adds another Java source directory to the already existing default src/flavorX/java.
Now all we need to do is to drop the common Customization activity file in src/common/java to make it available to the flavors one to three. The modified version required by flavorFour would go under its own source set at src/flavorFour/java.
So, the final project structure would look something like
+ App // module
|- src
|- common // shared srcDir
|- java
|- path/to/pkg
|- CustomizationActivity.java // inherited by flavors 1, 2, 3
+ flavorOne
+ flavorTwo
+ flavorThree
+ flavorFour
|- java
|- path/to/pkg
|- CustomizationActivity.java // per-flavor activity class
|- res
|- layout
|- activity_customization.xml // overrides src/main/res/layout
|- main
+ java
|- res
|- layout
|- activity_customization.xml // inherited by flavors 1, 2, 3
I use this to override codes for 5 years, just add a piece of code to your build.gradle.
For lastest gradle plugin(>=3.4.0):
android {
......
applicationVariants.configureEach { ApplicationVariant variant ->
AndroidSourceSet flavorSourceSet = android.sourceSets.findByName(variant.productFlavors[0].name);
if (flavorSourceSet != null) {
String flavorPath = flavorSourceSet.java.srcDirs[0].path;
variant.javaCompileProvider.configure { task ->
task.exclude { FileTreeElement elem ->
!elem.isDirectory() && !elem.file.parent.startsWith(flavorPath) &&
new File(flavorPath, elem.path).exists();
}
}
}
}
For older gradle plugin:
android {
......
applicationVariants.all { ApplicationVariant variant ->
AndroidSourceSet flavorSourceSet = android.sourceSets.findByName(variant.productFlavors[0].name);
if (flavorSourceSet != null) {
variant.javaCompiler.doFirst {
String flavorPath = flavorSourceSet.java.srcDirs[0].path;
variant.javaCompiler.exclude { FileTreeElement elem ->
!elem.isDirectory() && !elem.file.parent.startsWith(flavorPath) &&
new File(flavorPath, elem.path).exists();
}
}
}
}
It'll find duplicate classes in the Main sourceset and then exclude it during compile to avoid class duplicate error.
Ravi K Thapliyal's solution did not work for me with gradle 3.5.3
Here is an alternative solution that works:
Move the code common to all your flavors into a folder such as:
src/common/java
Then copy your generic flavors code into a generic src directory:
src/generic/java
That is where your generic version of the Customization.java class should be copied into.
Then, create a copy of your flavor specific code into a specific src directory e.g.
src/specific/java
Your build.gradle script should then be updated as follows:
android {
...
productFlavors {
flavor1 {
...
}
flavor2 {
...
}
flavor3 {
...
}
flavor4 {
...
}
}
sourceSets {
flavor1.java.srcDirs('src/generic/java', 'src/common/java')
flavor2.java.srcDirs('src/generic/java', 'src/common/java')
flavor3.java.srcDirs('src/generic/java', 'src/common/java')
flavor4.java.srcDirs('src/specific/java', 'src/common/java')
}
}
Note: Your Customization.java class should be removed from src/common/java
This solution works also for activity classes

Android Studio: Gradle Product Flavors: Define custom properties

I am building different product flavors of an Android App in Gradle (Android Studio).
Hence I defined the following product flavors:
android {
project.ext.set("customer", "")
project.ext.set("server", "")
//Configuration happens here - code removed for readability
buildTypes {
debug {
server = "test"
}
release {
server = "release"
}
}
//Available product flavors
productFlavors {
customerA{
customer = "a"
}
customerB{
customer = "b"
}
customerC{
customer = "c"
}
}
}
However, later on, when I access the defined project property "customer" (whose value is set in the product flavor i am currently building) in one of my build tasks, it always has the value "c" even though iam building customerA (in which case the property customer should be "a" rather than "c"). For instance I execute the following task later on:
preBuild << {
println "Building customer: " + customer
}
and it always prints:
Building customer: c
So i am guessing there is some overwriting happening? Possibly related to the configuration VS execution phase? Not sure how/why though, so any help is be greatly appreciated.
UPDATE: Alternatively it would already get me further to determine the name of the product flavor (without the build type name attached to it) and the build type (again: without the product flavor name prepended to it) during execution phase of the gradle build.
Considering the above configuration the expected product flavor names would be: customerA, customerB and customerC.
During evaluation phase, Gradle executes all of the code in your android block; it doesn't just execute the code relevant to the flavors you want to compile. In fact, during evaluation phase, it doesn't even really know what your flavors are; it has to evaluate that to find out.
So all three of your lines customer = "a", customer = "b", and customer = "c" will get executed.
This is one of the subtle things about Gradle that make it a little difficult to learn.
So I've explained why your code isn't working the way you expect, but this answer is incomplete because I haven't said a lot about what to do to make it work right, but it's hard to say what to do because I'm not sure what you're trying to accomplish. In general I can say that you should think of trying to accomplish what you want using user-defined tasks, and setting up intra-task dependencies to make sure things get executed in the right order. A gotcha with Android Gradle builds is that even those tasks don't get defined until evaluation phase (it can't know what tasks it needs to build all your flavors until it's evaluated the build file and knows what those flavors are), so do some SO sleuthing to see how to hook things onto Android Gradle build tasks -- you have to set up your tasks at the end of evaluation phase after the Android plugin has done its thing.
A lot of thanks goes to Scott Barta, for his suggestions and for explaining, why my solution did not work (which also made me reconsider a few things). I basically came up with different ways to accomplish what I needed.
Unless what you need to do can't be achieved by simply organizing your Android Resource tree based on build types and flavors (i.e. via convention) then I'd recommend option 2. Though I did keep option 1 for reference purposes since it covers the interesting subject of productFlavor property extension.
Custom property-based option: Product Flavors lets you define custom properties and thus extend a productFlavor. An example is provided here by Xavier Ducrohet: https://stackoverflow.com/a/17708357/1041533
I'll offer up a very simple and similar example as provided above, though in my case I needed a String property, rather than a boolean.
// This class will be used to create our custom property
class StringExtension {
String value
StringExtension (String value) {
this.value = value
}
public void setValue(String value) {
this.value = value
}
public String getValue() {
return value
}
}
android {
// Add our new property to each product flavor upon creation
productFlavors.whenObjectAdded { flavor ->
//I am suspecting the last argument is the default value
flavor.extensions.create("myProperty", StringExtension , '')
}
// then we can set the value on the extension of any flavor object
productFlavors {
customerA{
myProperty.value 'customerA'
}
customerB{
myProperty.value 'customerB'
}
}
}
//Adds a custom action to the preBuild task
preBuild << {
//Iterate over all application variants. We name our application variant object "variant" as indicated by "variant ->"
android.applicationVariants.all { variant ->
//Here we can iterate over the flavors of our variant, well call the flavor "flavor" as indicated by "flavor ->"
variant.productFlavors.each { flavor ->
//Access our custom property "customerName"
println "Building customer" + flavor.customerName.value
}
}
}
I then realized, that the above was totally unnecessary, because all I wanted was the name of my flavor (without the build type in it) and once I found the property that gives me the name of my flavor, I was able to change all of the above code as follows:
Simply use the name of your flavor as the customer's name by accessing the already existent product flavor property called "name".
android {
productFlavors {
customerA{
}
customerB{
}
}
}
//Adds a custom action to the preBuild task
preBuild << {
//Iterate over all application variants. We name our application variant object "variant" as indicated by "variant ->"
android.applicationVariants.all { variant ->
//Here we can iterate over the flavors of our variant, well call the flavor "flavor" as indicated by "flavor ->"
variant.productFlavors.each { flavor ->
//Access our product flavor name
println "Building customer" + flavor.name
}
}
}
The above makes a lot more sense too, because my directory structure for Android Resources is named after the actual flavors.
The latter also led me to my final solution for the original question:
Resource directory based approach
The intent was to modify a file in the xml folder of each customer based on whether it is a release or a debug build. This can be achieved by a corresponding folder structure. Based on the original question we have 3 customers, and each customer has a debug and a release build. The afore mentioned xml files are different for each customer and build type. Hence the following directory structure:
src/
- customerA
//Contains all relevant resource files specific to customer A
- customerB
//Contains all relevant resource files specific to customer B
- customerC
//Contains all relevant resource files specific to customer C
- customerADebug
//Contains debug server-settings file for customer A
- customerBDebug
//Contains debug server-settings file for customer B
- customerCDebug
//Contains debug server-settings file for customer C
- customerARelease
//Contains release server-settings file for customer A
- customerBRelease
//Contains release server-settings file for customer B
- customerCRelease
//Contains release server-settings file for customer C
So the main content for each product flavor was in the folder with the same name as the flavor (customerA, customerB etc. see first part of above snippet). Now this one file, that different based on whether it was a debug or release build for each customer is put into the appropriate folders such as customerADebug --> contains file with server settings for debug mode etc.
And when you build customerA for instance the correct file will be chosen if you build a debug or release build.
To answer the UPDATE part of my post:
Product flavor name (without buildType):
flavor.name (where flavor is a productFlavor)
The following worked for me to add custom properties to product flavors:
android {
// ...defaultConfig...
productFlavors.whenObjectAdded { flavor ->
// Add the property 'myCustomProperty' to each product flavor and set the default value to 'customPropertyValue'
flavor.ext.set('myCustomProperty', 'customPropertyValue')
}
productFlavors {
flavor1 {
}
flavor2 {
myCustomProperty = 'alternateValue'
}
}
}
flavor1 has the default value for the custom property, while flavor2 has the overridden value.
Here's an example how to access the custom property:
applicationVariants.all { variant ->
// Get the 'myCustomProperty' property from the variant's productFlavor (it's a list, but there should only be one)
def customProp = variant.productFlavors*.myCustomProperty[0]
}
I assume the same could be done to add custom properties to build types, but I haven't tested this.

PackageNameSuffix breaks test - Robolectric

When I add a packageNameSuffix to my build.gradle debug build type (see https://plus.google.com/108967384991768947849/posts/XGXH3arvbrK), all of my Robolectric tests are failing due to the following:
Caused by: android.content.res.Resources$NotFoundException: Unable to find resource ID #0x7f050000
at org.robolectric.shadows.ShadowResources.getResName(ShadowResources.java:354)
at org.robolectric.shadows.ShadowResources.openRawResource(ShadowResources.java:387)
at android.content.res.Resources.openRawResource(Resources.java)
at com.myapp.utilities.CSVUtility.parseRawResourceCSV(CSVUtility.java:38)
The problem seems to be that the raw folder src/main/res/main no longer being found. This folder contains a csv which is parsed at application start... which is where the tests go boom.
Architecture/data restructuring suggestions aside (I know CSV may not be the best way to get this data loaded at app start), does anyone know how I might remedy this problem?
Edit: I tried moving the file to the assets folder instead, and then my tests failed on a Context.getString() call. Resources look to be getting completely hosed when adding packageNameSuffixes.
Edit: tmurakami posted on the github issue - https://github.com/robolectric/robolectric/issues/1001#issuecomment-42740897
I've copied the full response:
Try using this gradle snippet.
This works fine in my environment.
def hasLibraryVariants = android.hasProperty('libraryVariants')
def variants = hasLibraryVariants ? android.libraryVariants : android.applicationVariants
tasks.withType(Test).whenTaskAdded {
it.systemProperty 'android.package', variants.iterator().next().processResources.packageForR
}
The original package name has been stored in the following fields of any variant:
variantData.variantConfiguration.originalPackageName
processResources.packageForR
generateBuildConfig.buildConfigPackageName
However these are internal only, so might become inaccessible in the future.
If you don't want to use these fields, try the following snippet:
tasks.withType(Test).whenTaskAdded {
it.systemProperty 'android.package', android.defaultConfig.packageName
}
To use this, you need to add the main package name in the 'android.defaultConfig' section.
android {
defaultConfig {
packageName 'main.package.name'
}
}
Looks like I need to add an android.package system property for the package name. See this issue conversation on Github - https://github.com/robolectric/robolectric/issues/1001

Categories

Resources