Dagger 2 Android Subcomponents override - android

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.

Related

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

Android Tests: use Dagger2 + Gradle

I understand how Dagger2 works,
I understand it allows to easily swap dependencies, so we can use mocks for testing.
Point is that I am not sure I understand how am I supposed to provide different Dagger2 Components implementations for testing and for debug/production.
Would I need to create 2 Gradle productFlavors (e.g "Production"/"Test")
that would contain 2 different Components definition?
Or can I specify that I want to use the mock Component for test compile and the non mock Component for non test builds?
I am confused, please some clarification would be great!
Thanks a lot!
Unit testing
Don’t use Dagger for unit testing
For testing a class with #Inject annotated constructor you don't need dagger. Instead create an instance using the constructor with fake or mock dependencies.
final class ThingDoer {
private final ThingGetter getter;
private final ThingPutter putter;
#Inject ThingDoer(ThingGetter getter, ThingPutter putter) {
this.getter = getter;
this.putter = putter;
}
String doTheThing(int howManyTimes) { /* … */ }
}
public class ThingDoerTest {
#Test
public void testDoTheThing() {
ThingDoer doer = new ThingDoer(fakeGetter, fakePutter);
assertEquals("done", doer.doTheThing(5));
}
}
Functional/integration/end-to-end testing
Functional/integration/end-to-end tests typically use the production
application, but substitute fakes[^fakes-not-mocks] for persistence,
backends, and auth systems, leaving the rest of the application to
operate normally. That approach lends itself to having one (or maybe a
small finite number) of test configurations, where the test
configuration replaces some of the bindings in the prod configuration.
You have two options here:
Option 1: Override bindings by subclassing modules
#Component(modules = {AuthModule.class, /* … */})
interface MyApplicationComponent { /* … */ }
#Module
class AuthModule {
#Provides AuthManager authManager(AuthManagerImpl impl) {
return impl;
}
}
class FakeAuthModule extends AuthModule {
#Override
AuthManager authManager(AuthManagerImpl impl) {
return new FakeAuthManager();
}
}
MyApplicationComponent testingComponent = DaggerMyApplicationComponent.builder()
.authModule(new FakeAuthModule())
.build();
Option 2: Separate component configurations
#Component(modules = {
OAuthModule.class, // real auth
FooServiceModule.class, // real backend
OtherApplicationModule.class,
/* … */ })
interface ProductionComponent {
Server server();
}
#Component(modules = {
FakeAuthModule.class, // fake auth
FakeFooServiceModule.class, // fake backend
OtherApplicationModule.class,
/* … */})
interface TestComponent extends ProductionComponent {
FakeAuthManager fakeAuthManager();
FakeFooService fakeFooService();
}
More about it in the official documentation testing page.

DI with Dagger2, add to or get from the graph?

Using Dagger2, what are the pros and cons of the 2 following implementations:
1)
public class MyReceiver extends BroadcastReceiver {
Something something;
#Override public void onReceive(Context context, Intent intent) {
something = ((MyApplication) context.getApplicationContext())
.getComponent().getSomething();
}
}
#Component
public interface MyComponent() {
Something getSomething();
}
2)
public class MyReceiver extends BroadcastReceiver {
#Inject Something something;
#Override public void onReceive(Context context, Intent intent) {
((MyApplication) context.getApplicationContext())
.getComponent().inject(this);
}
}
#Component
public interface MyComponent() {
void inject(MyReceiver myReceiver);
}
tl;dr Go for Option 2. #Inject your fields.
If your object can be created by injecting into the constructor (sample below), definitely go for Option 2, since you will not have to write any code other than marking the constructor to use with #Inject.
// object can just be injected without any additional modules
// since all dependencies can be satisfied
public class Something {
#Inject
public Something() {}
}
So to further answer the question let us concentrate on field / method injection.
Just using #Inject annotations will keep your code simpler. Option 2 will let you again just declare dependencies.
// dont't worry about where they come from
// or how they are created
#Inject
Something mSomething;
#Inject
Foo mFoo;
public void onCreate() {
((Application) context.getApplicationContext())
.getComponent().inject(this);
}
Also, for testing purposes you might want to override the injected fields, by just injecting the same object again with your TestComponent. This also is only possible with Option 2.
#Test
void someTest() {
// this is only possible with #Injected annotated fields
testComponent.inject(activityUnderTest);
}
But the first option!
The 1. Option will mostly be used to expose dependencies for dependent components, not to be confused with SubComponent components, which will always have full access to the parent graph.
#Component
public interface MyComponent() {
Something getSomething(); // exposes something
}
#Component(dependencies = {MyComponent.class})
public interface OtherComponent() {
// can access something in modules
}
Using Option 1 manually would mean to handle the dependency injection yourself by programmatically fetching dependencies from actual business code and writing harder to test code, which you are trying to avoid by using DI.

Dagger2: Is it possible to inject based on Android Version?

Is it possible I can use Dagger2 to inject a concrete implementation based on SDK version?
For example
// MediaPlayerComponent.class
#Component(modules = {MediaPlayerModule.class}
public interface MediaPlayerComponent
{
void inject(MediaPlayerUI ui)
}
// MediaPlayerUI.java
public class MediaPlayerUI
{
#Inject
public MediaPlayer mPlayer;
}
// GingerbreadMediaPlayer.java
public class GingerbreadMediaPlayer extends MediaPlayer {...}
// IceCreamSandwichMediaPlayer.java
public class IceCreamSandwichMediaPlayer extends MediaPlayer {...}
Yes, just decide which of both implementations should be returned in the MediaPlayerModule module in the concrete method annotated with #Provides, the one that returns a generic MediaPlayer.
Theoretically that's possible, I haven't done it before since I didn't have the need to but I think the following scenario would work:
You should have one parent ApplicationComponent that defines the needs of your app. Let's call it ApplicationComponent.
Now let's assume you want to have 2 different dependencies for API21 and above and for API below 21. You should create two components that inherits from your application component let's call them Application21Component and ApplicationBelow21Component.
For each of the two components you should have a different ApplicationModule that tells you application component how to get the dependencies needed
In your application class the body of "getComponent" function will be something similar to the following
public ApplicationComponent getComponent(){
if (mApplicationComponent == null) {
if (Build.VERSION.SDK_INT >= 21) {
mApplicationComponent = DaggerApplication21Component.builder()
.applicationModule(new ApplicationModule(this))
.build();
} else {
mApplicationComponent = DaggerApplicationBelow21Component.builder()
.applicationModule(new ApplicationModule(this))
.build();
}
}
return mApplicationComponent;
}

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
}

Categories

Resources