android gradle build set class attribute value - android

I want to set attribute values in my Application class from the build.gradle file like for example:
MyApplication.URL = "someurl.com"
that should be determined per build,
I tried :
productFlavors {
myApp {
qualified.package.path.MyApplication.URL = "someurl.com"
}
}
but it failed

You can write your fields inside BuildConfig class and then get them from there.
productFlavors {
myApp {
buildConfigField "String", "URL", "\"someurl.com\""
}
}
public class MyApplication extends Application {
private String URL;
#Override
public void onCreate() {
URL = BuildConfig.URL;
}
}

Related

How to pass customizable property when Android app launches

I would like to launch my Android app in such a way that I can set some external variable that my app can read. It would be nice if this was possible either in Gradle or as part of the debug/run configuration.
In essence, I would like to test for a variable to see if it is set. In this example I would like to set USE_FAKE_DATA:
if (USE_FAKE_DATA) {
...
} else {
...
}
One way is to use build variants and I have done this before. But I'm wondering if another way has been made available.
Gradle File
android {
buildTypes {
debug {
buildConfigField "boolean", "USE_FAKE_DATA", "true"
}
release {
buildConfigField "boolean", "USE_FAKE_DATA", "false"
}
}
}
Java File
class Test extends Activity {
#Override
public void onCreate(Bundle data) {
if (BuildConfig.USE_FAKE_DATA) {
...
} else {
...
}
}
}
Please refer this answer for more.

Dagger 2 Android Subcomponents override

if I create a subcomponent that I want to use in a specific feature with dagger lets say:
#TransactionsActivityScope
#Subcomponent(modules = {TransactionsModule.class})
public interface TransactionsComponent {
TransactionsManager provideTransactionsManager();
void inject(TransactionsFragment transactionsFragment);
void inject(TransactionsFilterActivity transactionsFilterActivity);
}
I add it in the main app component with a plus:
TransactionComponent plusTransactionSubcomponent(TransactionModule transactionModule);
and use it in the fragment:
public class TransactionsFragment {
..
..
..
#Override
protected void setupGraph(DaggerAppGraph graph) {
graph.plusTransactionsSubcomponent(new TransactionModule()).inject(this);
}
}
What is the correct way to override this subcomponent in Espresso tests.
For components and component dependencies it is straight forward where you just write a TestAppComponent that extends the "original" component and punch the MockModules in it, but how to do this cleanly with Subcomponents?
I also took a look at the Dagger AndroidInjector.inject(this); solution for components and activity components would be similar but I see no way to do it cleanly for subcomponents and fragments.
I believe it would be suboptimal to write methods and overrides the Activity/Fragments component setters and do the overrides there.
Am I missing something?
This was easy on the original Dagger, but not using Dagger 2. However, here is the solution: create a mocked flavor and a mocked module with exactly the same classname, filename and location. Now run your ui tests using the mocked flavor.
You can see in my test project how it is done.
I use the real module in my app. Located at src/prod/.../ContentRepositoryModule.java
I use a mocked module when testing: Located at src/mock/.../ContentRepositoryModule.java
My mocked module then references the FakeContentRepository, just as you were planning to do.
In the build.gradle:
flavorDimensions "api", "mode"
productFlavors {
dev21 {
// min 21 has faster build times, also with instant build
minSdkVersion 21
dimension "api"
}
dev16 {
minSdkVersion 16
dimension "api"
}
mock {
dimension "mode"
}
prod {
minSdkVersion 16
dimension "mode"
}
}
// remove mockRelease:
android.variantFilter { variant ->
if (variant.buildType.name == 'release'
&& variant.getFlavors().get(1).name == 'mock') {
variant.setIgnore(true);
}
}
So again: this test project shows it all.
In our app we use additional wrapper to manage subcomponents scope with name ComponentStorage. Our Application create this object, TestApplication overrides it and return TestComponentStorage. So we can easily override method plusTransactionSubcomponent and return component with mocked module.
public class ComponentStorage {
protected TransactionComponent transactionComponent;
protected AppGraph graph;
public ComponentStorage() {
graph = buildGraph();
}
public TransactionComponent plusTransactionSubcomponent(TransactionModule transactionModule) {
if(transactionComponent == null) {
transactionComponent = graph.plusTransactionsSubcomponent(new TransactionModule());
}
return transactionComponent;
}
public AppGraph buildGraph() {
return DaggerAppGraph.create();
}
// to manage scope manually
public void clearTransactionSubcomponent() {
transactionComponent = null;
}
}
public class TestComponentStorage extends ComponentStorage{
#Override
public TransactionComponent plusTransactionSubcomponent(TransactionModule transactionModule) {
if(transactionComponent == null) {
// mocked module
transactionComponent = graph.plusTransactionsSubcomponent(new TestTransactionModule());
}
return transactionComponent;
}
}
In client code you will use it componentStorage.plusTransactionsSubcomponent(new TransactionModule()).inject(this)
If you need full code, leave comment I will create gist for this.

Exclude code segment depending on flavor

I have an Android app with a lot of flavors, and I want only specific flavors to include a certain code segment. More specifically, I want to use a 3rd party library and add that library's init code only in specific flavors.
public class MainApplication
extends Application {
#Override
public void onCreate() {
super.onCreate();
//The library is only included in the build of specific flavors, so having this code in other flavors will not compile
//I want the following code only to be included in the flavors that include the library
SomeLibrary.init();
//other code that is relevant for all flavors
...
}}
A) Use reflection
defaultConfig {
buildConfigField "boolean", "USE_THE_CRAZY_LIB", "false"
}
productFlavors {
crazyFlavor {
buildConfigField "boolean", "USE_THE_CRAZY_LIB", "true"
//... all the other things in this flavor
}
}
then in your Application
public class MainApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
if (BuildConfig.USE_THE_CRAZY_LIB) {
Method method = clazz.getMethod("init", SomeLibrary.class);
Object o = method.invoke(null, args);
}
}
}
B) Use two different versions of the same class for two different flavors
(more information on that approach e.g. here)
For the other flavor (in src/otherFlavor/java):
public class FlavorController {
public static void init(){
}
}
For your flavor (in src/crazyFlavor/java):
public class FlavorController {
public static void init(){
SomeLibrary.init();
}
}
In your Application:
public class MainApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
FlavorController.init();
}
}
You can also use gradle to solve this issue by using with custom configurations.
Use this pattern to create custom configurations this line to your build.gradle file ,
configurations {
prodFlavBuildTypeCompile
}
dependencies {
prodFlavBuildTypeCompile 'com.google.code.gson:gson:2.8.0'
}
For example , My app flavours are free and paid with build types dev and prod
configurations {
freeDevCompile
freeProdCompile
}
dependencies {
freeDevCompile 'com.google.code.gson:gson:2.8.0'
}
And in the main folder keep Application with common code.
public class BaseApp extends Application {
#Override
public void onCreate() {
super.onCreate();
}
}
And use the implementation code in each product flavours.
public class ApplicationImpl extends BaseApp {
#Override
public void onCreate() {
super.onCreate();
SomeLibrary.init();
}
}
code for other flavours,
public class ApplicationImpl extends BaseApp {
#Override
public void onCreate() {
super.onCreate();
// code for flavour 2
}
}

How to pass BuildConfig values to dependency module?

I have some configured values in BuildConfig of App module. I want to pass those values to MyLib's BuildConfig which is dependency of App module. Is it possible?
This is how I shared the App Version Code and Version Name with a library/module BuildConfig :
Define version code and name in project level gradle file :
ext {
appVersionCode = 1
appVersionName = '1.0.1'
}
Now in your app gradle :
defaultConfig {
....
versionCode $rootProject.appVersionCode
versionName $rootProject.appVersionName
And in your library/module gradle :
defaultConfig {
....
def appVersionCode = $rootProject.appVersionCode
def appVersionName = '\"' + $rootProject.appVersionName +'\"'
versionCode appVersionCode
versionName appVersionName
//This part to get version code and name in library/module BuildConfig file
buildConfigField 'int', 'APP_VERSION_CODE', "$appVersionCode"
buildConfigField 'String', 'APP_VERSION_NAME', appVersionName
A simple project explaining how to share gradle buildConfigField variables with multiple modules : https://github.com/Noddy20/Multi-Module-Gradle-Config
The most easy way is to create a third module(library), and add this module to the dependency of your library module and app module.
Then put the shared build config to the shared third module.
app module <------------------ library module
^ ^
| |
| dependency |dependency
------------ third module -------
No, We cant do that. Dependency module can't access the BuildConfig file of App module.
The only alternative solution for your problem is you need add the same properties to your dependency BuildConfig file.
In general, BuildConfig has static members. so I would suggest reflection to transfer your BuildConfig as a list of model which holds Field/value
We would need a model to include field and value for all class members. Lets call it BuildConfigItem (I suggest to put this class in destination Module):
public class BuildConfigItem {
public final Field field;
public final Object object;
public BuildConfigItem(Field field, Object object) {
this.field = field;
this.object = object;
}
}
Now you can get all class members of BuildConfig with this method. Idea is to convert them to portable phase that can be retrieved on other module independently even without knowing what BuildConfig has:
public static ArrayList<BuildConfigItem> getBuildConfigField() {
ArrayList<BuildConfigItem> list = new ArrayList<>();
Field[] declaredFields = BuildConfig.class.getDeclaredFields();
BuildConfig buildConfig=new BuildConfig();
for (Field field : declaredFields) {
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
try {
BuildConfigItem buildConfigItem = new BuildConfigItem(field, field.get(buildConfig));
list.add(buildConfigItem);
} catch (IllegalAccessException e) {
Log.e(TAG, "error during assigning fields: ", e);
}
}
}
return list;
}
Get your list of BuildConfigItem :
ArrayList<BuildConfigItem> buildConfigItemArrayList = getBuildConfigField();
Then pass it to your module. Here is simple way how to iterate that list to get values:
for (BuildConfigItem buildConfigItem : buildConfigItemArrayList) {
Log.d(TAG,buildConfigItem.field.getName() + ":" + buildConfigItem.object);
}
Here is how to list all values and casting common types:
for (BuildConfigItem buildConfigItem : buildConfigItemArrayList) {
if (buildConfigItem.field.getType() == String.class) {
String value = (String) buildConfigItem.object;
Log.d(TAG, "String:" + buildConfigItem.field.getName() + ":" + value);
} else if (buildConfigItem.field.getType() == int.class) {
Integer value = (Integer) buildConfigItem.object;
Log.d(TAG, "integer:" + buildConfigItem.field.getName() + ":" + value);
} else if (buildConfigItem.field.getType() == boolean.class) {
Boolean value = (Boolean) buildConfigItem.object;
Log.d(TAG, "boolean:" + buildConfigItem.field.getName() + ":" + value);
} else {
Log.d(TAG, "Other:" + buildConfigItem.field.getName() + ":" + buildConfigItem.object);
}
}
Thats it 🙂
You would need to adjust this code if you define custom type of field in BuildConfig. i.e. Date or even more complex type.
Also be aware of that the destination module should have all dependencies of BuildConfig types. ( in case you are using your own object in defining field in BuildConfig)
Good luck,'.
The shortest inline solution. The example sets the VERSION_NAME from the app module to a sub-module with name common:
rootProject.findProject("common")?.let { commonProject ->
commonProject.plugins.whenPluginAdded {
val ext = commonProject.extensions.findByType(com.android.build.api.variant.AndroidComponentsExtension::class) ?: return#whenPluginAdded
ext.finalizeDsl {
ext.onVariants {
it.buildConfigFields.put("VERSION_NAME", BuildConfigField("String", "\"$versionName\"", ""))
}
}
}
}
This how I did this since I didn't want to maintain multiple BuildConfigs for different RN Modules.
Note: I am using react-native but this should be relevant to just vanilla Android\Java.
MainApplication.java
I passed the BuildConfig.class to the dependency constructor.
#Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> array =null;
try {
array= Arrays.asList(new MainReactPackage(),
new RNCustomPackageWithBuildConfig(BuildConfig.class));
} catch (IllegalAccessException e) {
Log.e(LOG_TAG,e.getMessage(),e.getCause());
}
return array;
}
RNCustomPackageWithBuildConfig.java (constructor accepts class param)
public class RNCustomPackageWithBuildConfig implements ReactPackage {
public static HashMap<String,Object> MainBuildConfig;
public RNCustomPackageWithBuildConfig(Class mainBuildConfig) throws IllegalAccessException {
MainBuildConfig= (HashMap<String, Object>) ObjectHelpers.getClassProperties(mainBuildConfig);
}
}
getClassProperties helper method
public class ObjectHelpers {
public static Map<String, Object> getClassProperties(final Class inClass) throws IllegalAccessException {
Map<String, Object> returnVal = new HashMap<>();
for (Field field : inClass.getDeclaredFields()) {
field.setAccessible(true); // You might want to set modifier to public first.
Object value = field.get(inClass);
if (value != null) {
returnVal.put(field.getName(),value);
}
}
return returnVal;
}
}
with in my Android RN module
private void handleIntent(Intent intent) throws Exception {
// insert business
String action = intent.getAction();
String type = intent.getType();
Log.d(LOG_TAG, "handleIntent => Type:" + type + " Action:" + action);
if (Intent.ACTION_VIEW.equals(action)) {
//access public static MainBuildConfig field from RNCustomPackageWithBuildConfig
String someBuildConfigField= RNCustomPackageWithBuildConfig .MainBuildConfig.get("SOME_BUILDCONFIG_FIELD").toString();
}

Override string in a library with explicit package

I know that i have to keep the same name to override a resource of a library, but imagine a class inside this library that is using a string.
With an example i'll be more clear i think:
Library class:
package com.library.randomlibrarypackage;
import com.library.R;
public class RandomClass extends Activity{
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
String stringFromResource = getString(R.string.librarystring);
}
}
This string is contained under library's values resources:
<resources>
<string name="librarystring">This is from library</string>
</resources>
If i try to "override" it and use RandomClass.class, the value of stringFromResource still remains "This is from library".
There's a way to override it without override the entire class?
You can try to override it in gradle build file for your library like this
android {
buildTypes {
debug{
resValue "string", "librarystring", "Library string debug"
}
release {
resValue "string", "librarystring", "Library string release"
}
}
}

Categories

Resources