How can "ask, don't look" be applied in Android? - android

This Google Testing Blog post lists some strategies for making code testable. One item says in part:
Ask for things, Don't look for things (aka Dependency Injection / Law of Demeter): OK, you got rid of your new operators in you application code. But how do I get a hold of the dependencies. Simple: Just ask for all of the collaborators you need in your constructor.
In other words, do this:
Foo(final Bar bar) {
mBar = bar;
}
Not this:
Foo() {
mBar = Bar.getBar(); // or new Bar();
}
The reason for this is obvious: it lets you test Foo by passing it a mock Bar. Since Android components require no-arg constructors, the equivalent is to pass their arguments via an extras Bundle.
How do you apply this principle in Android when the things the component needs are not Parcelable or Serializable?

What I use for that is Dagger2, where you only depend on the object graph (or one of its subscoped extended subgraphs) to receive all your dependencies.
Vaguely it works like this,
Singleton
.
#Component(modules={SingletonModule.class})
#Singleton
public interface SingletonComponent {
Foo foo();
Bar bar();
void inject(MainActivity mainActivity);
}
#Module
public class SingletonModule {
#Provides
#Singleton
public Bar bar() {
return new Bar();
}
#Provides
#Singleton
public Foo foo(Bar bar) {
return new Foo(bar);
}
}
public class CustomApplication extends Application {
SingletonComponent singletonComponent;
#Override
public void onCreate() {
super.onCreate();
singletonComponent = DaggerSingletonComponent.builder()
.singletonModule(new SingletonModule())
.build();
}
public SingletonComponent getSingletonComponent() {
return singletonComponent;
}
}
public class MainActivity extends Activity {
#Inject
Foo foo;
#Inject
Bar bar;
#Override
public void onCreate(Bundle saveinstanceState) {
super.onCreate(saveinstanceState);
((CustomApplication)getApplicationContext()).getSingletonComponent().inject(this);
bar.doSomething();
foo.doSomething();
}
}
Subscoping
.
#Component(modules=SingletonModule.class)
#Singleton
public interface SingletonComponent {
Foo foo();
}
#Component(dependencies={SingletonComponent.class}, modules={MainActivityModule.class})
#ActivityScope
public interface MainActivityCompoent extends SingletonComponent {
Bar bar();
void inject(MainActivity mainActivity);
}
#Module
public class SingletonModule {
#Provides
#Singleton
public Foo foo() {
return new Foo();
}
}
#Module
public class MainActivityModule {
#Provides
#ActivityScope
public Bar bar(Foo foo) {
return new Bar(foo);
}
}
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ActivityScope {
}
public class CustomApplication extends Application {
SingletonComponent singletonComponent;
#Override
public void onCreate() {
super.onCreate();
singletonComponent = DaggerSingletonComponent.builder()
.singletonModule(new SingletonModule())
.build();
}
public SingletonComponent getSingletonComponent() {
return singletonComponent;
}
}
public class MainActivity extends Activity {
#Inject
Foo foo;
#Inject
Bar bar;
private MainActivityComponent mainActivityComponent;
#Override
public void onCreate(Bundle saveinstanceState) {
super.onCreate(saveinstanceState);
mainActivityComponent = DaggerMainActivityComponent.builder()
.singletonComponent(((CustomApplication)getApplicationContext()).getSingletonComponent())
.mainActivityModule(new MainActivityModule())
.build();
mainActivityComponent.inject(this);
bar.doSomething();
foo.doSomething();
}
}

Related

Dagger 2 returns null after injection

I am trying to make an injection using Dagger 2, but it always returns null. I think I am doing all right, but anyway it does not work.
Here is the application class:
public class ApplicationSA extends Application {
private static AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.create();
}
public static AppComponent getComponent() {
return appComponent;
}
}
The component interface:
#Component(modules = {
SnoreDetectorClass.class,
AudioRecorderClass.class
})
public interface AppComponent {
void injectsMainFunctionalityActivity(Activity activity);
}
An the main class where I am trying to get the object:
public class MainFunctionalityActivity extends AppCompatActivity {
#Inject
AudioRecorderClass audioRecorderClass;
#Inject
SnoreDetectorClass snoreDetectorClass;
#Override
protected void onCreate(Bundle savedInstanceState) {
ApplicationSA.getComponent().injectsMainFunctionalityActivity(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("TESTING","audioRecorderClass= "+audioRecorderClass); // always null
Log.d("TESTING","snoreDetectorClass= "+snoreDetectorClass); // always null
...
}
And here are the module classes:
#Module
public class AudioRecorderClass {
public interface AudioRecorderInterface {
void AudioRecorder_hasUpdate(double amplitude_in_dB);
}
public AudioRecorderInterface delegate = null;
#Provides
AudioRecorderClass provideAudioRecorderClass(Activity activity) {
delegate = (AudioRecorderInterface)activity;
return new AudioRecorderClass();
}
...
#Module
public class SnoreDetectorClass {
#Provides
SnoreDetectorClass provideSnoreDetectorClass() {
return new SnoreDetectorClass();
}
...
What am I doing wrong ? Why the objects are always null ?
Ah, I see what is going on here. You cannot inject into a subclass. So in your AppComponent you cannot have
void injectsMainFunctionalityActivity(Activity activity);
you must inject with
void injectsMainFunctionalityActivity(MainFunctionalityActivity activity);
As a side note I would suggest not combining your injector and your model class. Better to have separation of concerns. Keep them separate
You have to specifically tell dagger which activity will be injected here, not use the super class Activity but rather your own implementation of the Activity class :
void injectsMainFunctionalityActivity(Activity activity);
change to:
void injectsMainFunctionalityActivity(MainFunctionalityActivity activity);

Using Dagger and Robolectric with test application

I am using MVP pattern with a Fragment(GalleryFragment), where Application class(MainApplication) sources MainActivityRepository and GalleryFragmentPresenter(grouped as DIModules) which are provided to Fragment through field injection.
To test GalleryFragment in isolation, my idea was to use Robolectric configuration(#Config) to replace MainApplication entirely with a custom TestApplication sourcing mockDIModules.
GalleryFragmentTest runs until startFragment(galleryFragment) but I get a NullPointerException at MainApplication.getComponent().inject(this); inside GalleryFragment.
I suspect this is because this line specifically uses MainApplication while everything else is dealt with TestApplication set by Robolectric #Config, but I'm not sure and I am looking for advice on how to successfully run tests using this custom TestApplication.
While searching for possible solutions, I found out about using AndroidInjector from Dagger support library, which will get rid of MainApplication.getComponent().inject(this); entirely but would this work?
https://android.jlelse.eu/android-and-dagger-2-10-androidinjector-5e9c523679a3
GalleryFragment.java
public class GalleryFragment extends Fragment {
#Inject
public MainActivityRepository mRepository;
#Inject
public GalleryFragmentPresenter mGalleryFragmentPresenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainApplication.getComponent().inject(this); //NullPointerException here
mGalleryFragmentPresenter.initialize(mRepository, value);
}
}
DIModules.java
#Module
public class DIModules {
#Provides
public GalleryFragmentPresenter provideGalleryFragmentPresenter(){
return new GalleryFragmentPresenter();
}
#Provides
#Singleton
public MainActivityRepository provideMainActivityRepository(){
return new MainActivityRepository();
}
}
AppComponent.java
#Singleton
#Component(modules = DIModules.class)
public interface AppComponent {
void inject(GalleryFragment galleryFragment);
}
MainApplication.java
public class MainApplication extends Application {
public static AppComponent component;
#Override
public void onCreate() {
super.onCreate();
Realm.init(this);
component = buildComponent();
}
public static AppComponent getComponent() {
return component;
}
protected AppComponent buildComponent(){
return DaggerAppComponent
.builder()
.dIModules(new DIModules())
.build();
}
}
TestApplication.java
public class TestApplication extends Application {
public static AppComponent component;
#Override
public void onCreate() {
super.onCreate();
component = buildComponent();
}
public static AppComponent getComponent() {
return component;
}
protected AppComponent buildComponent(){
return DaggerAppComponent.builder()
.dIModules(new mockDIModules())
.build();
}
}
GalleryFragmentTest.java
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class,
application = TestApplication.class)
public class GalleryFragmentTest {
#Test
public void allItemTabTest() throws Exception {
GalleryFragment galleryFragment = GalleryFragment.newInstance(value);
startFragment(galleryFragment);
assertNotNull(galleryFragment);
}
}
I am using dagger, dagger-android-support, dagger-compiler version 2.14.1 and robolectric:3.6.1
Of course, it is null. Your fragment still tries to work with production application while you're doing things in the test application.
Change your injection code to next:
((MainApplication) getContext().getApplicationContext()).getComponent().inject(this);
And also make the method in your Application getComponent() as not static, so test app overrides it.
Another option is to change your TestApplication to next:
public class TestApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
buildComponent();
}
private void buildComponent(){
Application.component = DaggerAppComponent.builder()
.dIModules(new mockDIModules())
.build();
}
}

Dagger2 - "Unused" Modules in Generated Component Class

My Dagger2 Component class contains 3 modules which I'm trying to use to inject field dependencies into an Android Activity class. The generated Component file has comments saying all the modules are unused, linking this page for more info.
My Activity class is calling the Component's inject(Activity) method and has fields annotated for injection that are provided by the modules, so I am not sure why the generated Component file does not have any Providers to do this injection.
My code is below, thanks for the help!
Generated Component Class:
public final class DaggerMainComponent implements MainComponent {
private DaggerMainComponent(Builder builder) {
assert builder != null;
}
public static Builder builder() {
return new Builder();
}
public static MainComponent create() {
return builder().build();
}
#Override
public void inject(Activity activity) {
MembersInjectors.<Activity>noOp().injectMembers(activity);
}
public static final class Builder {
private Builder() {}
public MainComponent build() {
return new DaggerMainComponent(this);
}
/**
* #deprecated This module is declared, but an instance is not used in the component. This method is a no-op. For more, see https://google.github.io/dagger/unused-modules.
*/
#Deprecated
public Builder daoModule(DaoModule daoModule) {
Preconditions.checkNotNull(daoModule);
return this;
}
/**
* #deprecated This module is declared, but an instance is not used in the component. This method is a no-op. For more, see https://google.github.io/dagger/unused-modules.
*/
#Deprecated
public Builder repositoryModule(RepositoryModule repositoryModule) {
Preconditions.checkNotNull(repositoryModule);
return this;
}
/**
* #deprecated This module is declared, but an instance is not used in the component. This method is a no-op. For more, see https://google.github.io/dagger/unused-modules.
*/
#Deprecated
public Builder portableModule(PortableModule portableModule) {
Preconditions.checkNotNull(portableModule);
return this;
}
}
}
Non-Generated Component Class:
#Component(modules={DaoModule.class,RepositoryModule.class,PortableModule.class})
public interface MainComponent {
void inject(Activity activity);
}
Module Classes:
Is there any issue with having one module provide an object with a dependency on another object provided by another module belonging to the same Component?
#Module
public class DaoModule {
private DatabaseHelper databaseHelper;
public DaoModule(DatabaseHelper databaseHelper){
this.databaseHelper = databaseHelper;
}
#Provides
public Dao<Player,Integer> providePlayerDao(){
return databaseHelper.getPlayerDao();
}
#Provides
public Dao<GamePlayed,Integer> provideGamePlayedDao() {
try {
return databaseHelper.getDao(GamePlayed.class);
} catch (SQLException e) {
return null;
}
}
#Provides
public Dao<GamePlayer,Integer> provideGamePlayerDao() {
try {
return databaseHelper.getDao(GamePlayer.class);
} catch (SQLException e) {
return null;
}
}
}
...
#Module
public class RepositoryModule {
#Provides
public IGameResultRepository provideGameResultRepository(
Dao<Player,Integer> playerDao,
Dao<GamePlayed,Integer> gameDao,
Dao<GamePlayer, Integer> gamePlayerDao)
{
return new OrmliteGameResultRepository(playerDao,gameDao,gamePlayerDao);
}
}
#Module
public class PortableModule {
#Provides
public GameResultListener provideGameResultListener(IGameResultRepository gameResultRepository){
return new GameResultListener(gameResultRepository);
}
}
Application Class:
public class AppStart extends Application {
private MainComponent mainComponent;
#Override
public void onCreate() {
super.onCreate();
DatabaseHelper databaseHelper = new DatabaseHelper(getApplicationContext());
mainComponent = DaggerMainComponent.builder()
.daoModule(new DaoModule(databaseHelper))
.build();
}
public MainComponent getMainComponent(){
return mainComponent;
}
}
Activity Class:
public class MyActivity extends Activity {
#Inject GameResultListener gameResultListener;
#Inject Dao<Player,Integer> dao;
#Inject IGameResultRepository repository;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
((AppStart)this.getApplication()).getMainComponent().inject(this);
Question 1: Why are my modules being marked as "unused"?
You have not supplied the correct injection site! As it stands, your component interface is one with the sole injection site of android.app.Activity. Since android.app.Activity has no #Inject annotations on its fields then you get a no-op members injector. Similarly, your modules are marked as unused because none of them are actually being used as sources of dependencies for android.app.Activity. To fix this, in your component change:
void inject(Activity activity);
to:
void inject(MyActivity myActivity);
Question 2:
Is there any issue with having one module provide an object with a dependency on another object provided by another module belonging to the same Component?
No, this is perfectly fine. To illustrate, let's take a simple object graph:
public class Foo {
public Foo(FooDependency fooDependency) {}
}
public class FooDependency {
FooDependency(String name) {}
}
We want to inject it inside the following class using Dagger:
public class FooConsumer {
#Inject Foo foo;
private FooConsumer() {}
}
We would like to reuse a module binding FooDependency so we'll write two separate modules:
#Module
public class FooModule {
#Provides
Foo foo(FooDependency fooDependency) {
return new Foo(fooDependency);
}
}
#Module
public class FooDependencyModule {
#Provides
FooDependency fooDependency() {
return new FooDependency("name");
}
}
And the following component interface:
#Component(modules = {FooModule.class, FooDependencyModule.class})
public interface FooComponent {
void inject(FooConsumer fooConsumer);
}
The generated component DaggerFooComponent contains the following code that will correctly use the FooDependency from the separate module FooDependencyModule to inject Foo:
#SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.fooDependencyProvider =
FooDependencyModule_FooDependencyFactory.create(builder.fooDependencyModule);
this.fooProvider = FooModule_FooFactory.create(builder.fooModule, fooDependencyProvider);
this.fooConsumerMembersInjector = FooConsumer_MembersInjector.create(fooProvider);
}

I don't know why the object injected by dagger2 is null in presenter

I don't know why loginResponseHandler of MainRankPresenter.java injected by dagger2 is null in MainRankPresenter.
I just want to inject to field for field injection.
Should I do other way instead field injection?
please, Let me know how to resolve it.
BBBApplication.java
public class BBBApplication extends MultiDexApplication
{
...
#Override
public void onCreate() {
super.onCreate();
initAppComponent();
}
private void initAppComponent() {
this.appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
public static BBBApplication get(Context ctx) {
return (BBBApplication) ctx.getApplicationContext();
}
public AppComponent getAppComponent() {
return this.appComponent;
}
...
}
AppModule.java
#Module
public class AppModule {
private BBBApplication application;
public AppModule(BBBApplication application) {
this.application = application;
}
#Provides
#Singleton
public Application provideApplication() {
return this.application;
}
#Provides
#Singleton
public Resources provideResources() {
return this.application.getResources();
}
#Provides`enter code here`
#Singleton
public SharedPreferences provideSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(this.application);
}
}
AppComponent.java
#Singleton
#Component(modules = {AppModule.class, ServiceModule.class})
public interface AppComponent {
RankFragmentComponent plus(RankFragmentModule module);
Application application();
Resources resources();
}
RankFragmentModule.java
#Module
public class RankFragmentModule {
private RankFragment rankFragment;
public RankFragmentModule(RankFragment rankFragment) {
this.rankFragment = rankFragment;
}
#Provides
#ActivityScope
public LoginResponseHandler provideLoginResponseHandler() {
return new LoginResponseHandler(this.rankFragment);
}
#Provides
#ActivityScope
// #Named("rankFragment")
public RankFragment provideRankFragment() {
return this.rankFragment;
}
#Provides
#ActivityScope
public MainRankPresenter provideMainRankPresenter(RankFragment rankFragment) {
return new MainRankPresenter(new MainRankViewOps(rankFragment));
}
}
RankFragmentComponent.java
#ActivityScope
#Subcomponent(modules = {RankFragmentModule.class})
public interface RankFragmentComponent {
void inject(RankFragment rankFragment);
}
RankFragment.java
public class RankFragment extends Fragment {
#Inject
MainRankPresenter presenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BBBApplication.get(getContext())
.getAppComponent()
.plus(new RankFragmentModule(this))
.inject(this);
presenter.test();
}
MainRankPresenter.java
public class MainRankPresenter implements Presenter {
private MainRankViewOps viewOps;
#Inject
LoginResponseHandler loginResponseHandler;
#Inject
public MainRankPresenter(MainRankViewOps viewOps) {
this.viewOps = viewOps;
}
#Override
public void test() {
Log.d(Constants.TAG_DEBUG, "=== presenter test" + this.toString());
Log.d(Constants.TAG_DEBUG, "=== " + loginResponseHandler.toString());
this.viewOps.initViewOps();
}
}
MainRankViewOps.java
public class MainRankViewOps implements ViewOps {
RankFragment fragment;
#Inject
public MainRankViewOps(RankFragment fragment) {
this.fragment = fragment;
}
#Override
public void initViewOps() {
Log.d(Constants.TAG_DEBUG, "=== view ops" + this.toString());
Log.d(Constants.TAG_DEBUG, "=== " + fragment.toString());
}
}
Injection by Dagger 2 is not recursive. Therefore, when you call inject(this) in RankFragment only #Inject annotated fields of that fragment are being injected. Dagger 2 will not search for #Inject annotations in the injected objects.
In general, you should attempt to restrict usage of Dependency Injection frameworks to "top-level" components (Activities, Fragments, Services, etc.) which are being instantiated by Android framework for you. In objects that you instantiate yourself (like MainRankPresenter) you should use other DI techniques which do not involve external framework (e.g. dependency injection into constructor).
Because you #Provides the MainRankPresenter, Dagger won't inject it: you take responsibility for this. You could possibly have your provides method be passed a MembersInjector si you can inject the fields if the object before returning it, but it'd probably be better to refactor your module you remove that provides method and let Dagger handle the injection (you have all the #Inject needed already)

Dagger 2 base class injections

In Dagger 1 I had a base class setup such that it would handle creating a scoped graph and injecting dependencies into the current object. For example...
public abstract class MyBaseActivity extends Activity {
private ObjectGraph graph;
protected void onCreate(Bundle savedInstanceState) {
graph = ((MyApp) getApplication()).plus(getModules());
graph.inject(this);
}
protected Object[] getModules();
}
public class MyClass extends MyBaseActivity {
#Inject SomeDep someDep;
#Override
protected Object[] getModules() {
return new Object[/* Contains a module that provides SomeDep */];
}
}
This allowed for each subclass to supplement their own set of modules in addition to a standard application module.
After playing around with Dagger 2, it doesn't seem possible to handle a similar scenario...
public abstract class MyBaseActivity extends Activity {
private MyBaseActivityComponent component;
protected void onCreate(Bundle savedInstanceState) {
component = ((MyApp) getApplication()).component().plus(/* Can not accept an array */);
component.inject(this);
}
}
I worked around the above by modifying MyBaseActivityComponent such that it would list all possible modules it may use...
#Subcomponent(modules = {
Module1.class,
Module2.class
})
public interface MyBaseActivityComponent {
public void inject(MyBaseActivity activity);
}
So now I can do something like this...
public abstract class MyBaseActivity extends Activity {
private MyBaseActivityComponent component;
protected void onCreate(Bundle savedInstanceState) {
component = ((MyApp) getApplication()).component().plus(new Module1(), new Module2());
component.inject(this);
}
}
But now I have a problem where the injection will inject dependencies for MyBaseActivity but not it's subclasses. Suggestions?
Theoretically, you can do it like this.
1.) Specify a child scope
#Scope
#Retention(RUNTIME)
public #interface PerActivity {
}
2.) Specify the parent component
#Singleton
#Component(modules={Module1.class, Module2.class)
public interface MyApplicationComponent {
Dependency1 providesDependency1();
Dependency2 providesDependency2();
}
3.) Specify the child component
#PerActivity
#Component(dependencies={MyApplicationComponent.class}, modules={Module3.class})
public interface MyBaseActivityComponent extends MyApplicationComponent {
void inject(BaseActivity baseActivity);
Dependency3 providesDependency3();
}
4.) Create your module
#Module
public class Module3 {
#Provides
#PerActivity
public Dependency3 providesDependency3() {
return new Dependency3();
}
}
5.) Create Activity-level scoped component
public class BaseActivity extends AppCompatActivity {
private MyBaseActivityComponent baseComponent;
#Override
public void onCreate(Bundle saveState) {
super.onCreate(saveState);
baseComponent = DaggerBaseActivityComponent.builder()
.applicationComponent(((MyApp)getApplication()).component())
.build();
}
public MyBaseActivityComponent baseComponent() {
return baseComponent;
}
#Override
public void onDestroy() {
component = null;
super.onDestroy();
}
}
Please reply if it worked, previously I forgot to specify the dependencies in my Component and got compile errors, but it should work like this.
Also, if you need to specify a subcomponent for each Activity, then you can just specify the dependencies with provision methods in the BaseActivityComponent component...
#PerActivity
#Component(dependencies={MyBaseActivityComponent.class}, modules={Module4.class})
public interface MyActivityComponent extends MyBaseActivityComponent {
public void inject(MyActivity myActivity);
Dependency4 providesDependency4();
}
#Module
public class Module4 {
#PerActivity
#Provides
public Dependency4 providesDependency4(Dependency3 dependency3) {
return new Dependency4(dependency3);
}
}
public class MyActivity extends MyBaseActivity {
private MyActivityComponent component;
#Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
component = DaggerMyActivityComponent.builder()
.applicationComponent(((MyApp)getApplication()).component())
.myBaseActivityComponent(baseComponent())
.build();
}
#Override
public void onDestroy() {
component = null;
super.onDestroy();
}
}
EDIT: #Subcomponent works to replace component dependencies with subcomponent factory methods according to the docs only if you use the following pattern (aka, embedding the subcomponent within the parent component using a provision/factory method definition):
#Singleton #Component
interface ApplicationComponent {
// component methods...
RequestComponent newRequestComponent(RequestModule requestModule);
}
Where
#Subcomponent(modules={RequestModule.class})
interface RequestComponent {
RequestSomething requestSomething();
}

Categories

Resources