Exclude code segment depending on flavor - android

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
}
}

Related

Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath

I'm trying out the new CameraX, have followed the instructions mentioned here https://developer.android.com/jetpack/androidx/releases/camera#camera-camera2-1.0.0-alpha07. And I get the following error,
e: Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath:
class com.sample.SampleApplication, unresolved supertypes: androidx.camera.core.CameraXConfig.Provider
Can someone help?
Edit: Another thing, I extend "MultiDexApplication" class.
your Application should look similar to this:
public class AppController extends Application implements CameraXConfig.Provider {
and the implemented method like this:
#NonNull
#Override
public CameraXConfig getCameraXConfig() {
return Camera2Config.defaultConfig(this);
}
works for me with the same setup you posted in the google group
ie
def camerax_version = "1.0.0-alpha07"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-view:1.0.0-alpha04"
in the Activity all I do is:
bind cameraView and bind it to the activity:
#BindView(R.id.cameraView)
CameraView cameraView;
....
if (checkCameraAndStoragePermissions()) {
initializeView();
} else {
askCameraAndStoragePermissions(new SingleObserver<Boolean>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Boolean response) {
if (response) {
initializeView();
}
}
#Override
public void onError(Throwable e) {
}
});
}
#SuppressLint("MissingPermission")
private void initializeView() {
...
cameraView.bindToLifecycle(this);
...
}
api "androidx.camera:camera-core:${camerax_version}" fixes the problem

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.

android gradle build set class attribute value

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;
}
}

How to detect whether android app is running UI test with Espresso

I am writing some Espresso tests for Android. I am running in the the following problem:
In order for a certain test case to run properly, I need to disable some features in the app. Therefore, in my app, I need to detect whether I am running Espresso test so that I can disable it. However, I don't want to use BuildConfig.DEBUG to because I don't want those features to be disabled in a debug build. Also, I would like to avoid creating a new buildConfig to avoid too many build variants to be created (we already have a lot of flavors defined).
I was looking for a way to define buildConfigField for test but I couldn't find any reference on Google.
Combining Commonsware comment + Comtaler's solution here's a way to do it for any test class using the Espresso framework.
private static AtomicBoolean isRunningTest;
public static synchronized boolean isRunningTest () {
if (null == isRunningTest) {
boolean istest;
try {
// "android.support.test.espresso.Espresso" if you haven't migrated to androidx yet
Class.forName ("androidx.test.espresso.Espresso");
istest = true;
} catch (ClassNotFoundException e) {
istest = false;
}
isRunningTest = new AtomicBoolean (istest);
}
return isRunningTest.get();
}
Combined with CommonsWare's comment. Here is my solution:
I defined an AtomicBoolean variable and a function to check whether it's running test:
private AtomicBoolean isRunningTest;
public synchronized boolean isRunningTest () {
if (null == isRunningTest) {
boolean istest;
try {
Class.forName ("myApp.package.name.test.class.name");
istest = true;
} catch (ClassNotFoundException e) {
istest = false;
}
isRunningTest = new AtomicBoolean (istest);
}
return isRunningTest.get ();
}
This avoids doing the try-catch check every time you need to check the value and it only runs the check the first time you call this function.
How about a flag in BuildConfig class?
android {
defaultConfig {
// No automatic import :(
buildConfigField "java.util.concurrent.atomic.AtomicBoolean", "IS_TESTING", "new java.util.concurrent.atomic.AtomicBoolean(false)"
}
}
Add this somewhere in your test classes.
static {
BuildConfig.IS_TESTING.set(true);
}
Building on the answers above the following Kotlin code is equivalent:
val isRunningTest : Boolean by lazy {
try {
Class.forName("android.support.test.espresso.Espresso")
true
} catch (e: ClassNotFoundException) {
false
}
}
You can then check the value of the property:
if (isRunningTest) {
// Espresso only code
}
i prefer not to use reflection which is slow on android. Most of us have dagger2 set up for dependency injection. I have a test component set up for testing. Here is a brief way you can get the application mode (testing or normal):
create a enum:
public enum ApplicationMode {
NORMAL,TESTING;
}
and a normal AppModule:
#Module
public class AppModule {
#Provides
public ApplicationMode provideApplicationMode(){
return ApplicationMode.NORMAL;
}
}
create a test runner like me:
public class PomeloTestRunner extends AndroidJUnitRunner {
#Override
public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return super.newApplication(cl, MyTestApplication.class.getName(), context);
}
}
dont forget to declare it in gradle like this:
defaultConfig {
testInstrumentationRunner "com.mobile.pomelo.base.PomeloTestRunner"
}
Now create a subclass of the AppModule with override method that looks exactly like this and do not mark it as a module above the class definition :
public class TestAppModule extends AppModule{
public TestAppModule(Application application) {
super(application);
}
#Override
public ApplicationMode provideApplicationMode(){
return ApplicationMode.TESTING; //notice we are testing here
}
}
now in your MyTestApplication class that you declared in custom test runner have the following declared:
public class PomeloTestApplication extends PomeloApplication {
#Singleton
#Component(modules = {AppModule.class})
public interface TestAppComponent extends AppComponent {
}
#Override
protected AppComponent initDagger(Application application) {
return DaggerPomeloTestApplication_TestAppComponent.builder()
.appModule(new TestAppModule(application)) //notice we pass in our Test appModule here that we subclassed which has a ApplicationMode set to testing
.build();
}
}
Now to use it simply inject it in production code wherever like this:
#Inject
ApplicationMode appMode;
so when your running espresso tests it will be testing enum but when in production code it will be normal enum.
ps not necessary but if you need to see how my production dagger builds the graph its like this and declared in application subclass:
protected AppComponent initDagger(Application application) {
return DaggerAppComponent.builder()
.appModule(new AppModule(application))
.build();
}
If you are using JitPack with kotlin. You need to change Espresso's package name .
val isRunningTest : Boolean by lazy {
try {
Class.forName("androidx.test.espresso.Espresso")
true
} catch (e: ClassNotFoundException) {
false
}
}
For checking
if (isRunningTest) {
// Espresso only code
}
I'll create two files like below
src/main/.../Injection.java
src/androidTest/.../Injection.java
And in Injection.java I'll use different implementation, or just a static variable int it.
Since androidTest is the source set, not a part of build type, I think what you want to do is hard.
You can use SharedPreferences for this.
Set debug mode:
boolean isDebug = true;
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt("DEBUG_MODE", isDebug);
editor.commit();
Check if debug mode:
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
boolean isDebug = sharedPref.getBoolean("DEBUG_MODE", false);
if(isDebug){
//Activate debug features
}else{
//Disable debug features
}
Here is a way to adapt the accepted solution for a react-native Android App.
// MainActivity.java
// ...
#Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
// ...
#Override
protected Bundle getLaunchOptions() {
Bundle initialProperties = new Bundle();
boolean testingInProgress;
try {
Class.forName ("androidx.test.espresso.Espresso");
testingInProgress = true;
} catch (ClassNotFoundException e) {
testingInProgress = false;
}
initialProperties.putBoolean("testingInProgress", testingInProgress);
return initialProperties;
}
};
}
}
You will then be able to access testingInProgress as a prop given to your top-most component (typically App.js). From there you can use componentDidMount or equivalent to access it and throw it into your Redux store (or whatever you are using) in order to make it accessible to the rest of your app.
We use this to trigger some logic in our app to assist us taking screenshots with fastlane.
I would suggest using a boolean variable initialized to false in another class called, for instance, Settings.java:
private static boolean isRunningAndroidTest = false;
This boolean variable would have following setter and getter also defined in Settings.java:
public static void setIsRunningAndroidTest(boolean isRunningAndroidTest) {
Settings.isRunningAndroidTest = isRunningAndroidTest;
}
public static boolean getIsRunningAndroidTest() {
return isRunningAndroidTest;
}
One could then toggle this isRunningAndroidTest variable to true at the beginning of the androidTest file by calling the setter defined in Settings.java as follows:
Settings.setIsRunningAndroidTest(true);
Finally, the actual value of this boolean variable can later be checked in any other files by calling its corresponding getter defined in Settings.java as follows:
if (Settings.getIsRunningAndroidTest()) {
// Do something in case an androidTest is currently running
} else {
// Do something else in case NO androidTest is currently running
}

Dagger used in Robolectric tests

I want to use Dagger injections in my robolectric tests but I have trouble with set it up. Where is error in my code sample. How can I make this work?
My main module
#Module(
includes = DatabaseModule.class,
injects = {
MainActivity.class,
}
)
public class MainModule {
private final MyApplication application;
public MainModule(MyApplication application) {
this.application = application;
}
My test module
#Module(
overrides = true,
includes = MainModule.class,
injects = {
TestMyApplication.class,
MyApplication.class
}
)
public class TestModule {
}
My production main class
public class MyApplication extends Application {
#Inject
public MyApplication() {
super();
}
#Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(getModules().toArray());
inject(this);
}
...
}
My robolectric test application class
public class TestMyApplication extends MyApplication {
#Override
protected List<Object> getModules() {
List<Object> modules = super.getModules();
return modules;
}
public void injectMocks(Object object) {
((TestMyApplication) Robolectric.application).inject(object);
}
Error:
java.lang.RuntimeException: java.lang.IllegalArgumentException: No inject registered for members/info.korzeniowski.MyApplication.TestMyApplication. You must explicitly add it to the 'injects' option in one of your modules.
When I change in My robolectric application class method to this:
#Override
protected List<Object> getModules() {
List<Object> modules = super.getModules();
modules.add(new TestModule());
return modules;
}
Result:
java.lang.RuntimeException: java.lang.IllegalStateException: Module adapter for class info.korzeniowski.walletplus.test.TestModule could not be loaded. Please ensure that code generation was run for this module.
Update
gradle.build:
compile 'com.jakewharton:butterknife:5.1.2'
def daggerVersion = '1.2.+'
apt "com.squareup.dagger:dagger-compiler:$daggerVersion"
compile "com.squareup.dagger:dagger:$daggerVersion"
compile "com.squareup:javawriter:2.2.1"
You should provide the TestModule and you are doing it right (TestModule must be in the list of modules returned from getModules). You can find solution for the second error here: Dagger example built through eclipse fails with 'Please ensure that code generation was run for this module.'

Categories

Resources