3

Usually when using Dagger 2 and android I have the following:

@Singleton 
@Component(modules = {ApplicationModule.class}) 
public interface ApplicationComponent {


    void inject(MainActivity activity); 
}

public class MainActivity extends Activity { 
    @Inject SharedPreferences mSharedPrefs; 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        ((DemoApplication) getApplication())
                    .getComponent()
                    .inject(this); 
    } 
}

But recently I have seen this:

@Singleton 
@Component(modules = {ApplicationModule.class}) 
public interface ApplicationComponent { 
    SharedPreferences getSharedPreferences(); 
}

public class MainActivity extends Activity { 
    SharedPreferences mSharedPrefs; 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        mSharedPrefs = ((DemoApplication) getApplication())
                .getComponent().getSharedPreferences();
    } 
}

I have omitted the DemoApplication class and the Module class, they are standard.

What is the difference between these two approaches? Pro's and con's of either? Maybe a right or wrong way?

im_not_josh
  • 483
  • 3
  • 10
  • Related: [What is the purpose of the non-inject methods in components in Dagger 2](https://stackoverflow.com/questions/41472319/what-is-the-purpose-of-the-non-inject-methods-in-components-in-dagger-2/41472320) – David Rawson Jan 26 '17 at 05:10
  • Thanks for this, I did a search but didnt find this – im_not_josh Jan 27 '17 at 06:18

2 Answers2

2

The dependency inversion principle of software engineering suggests that we should try and depend on abstractions rather than concretions.

For this reason, you should prefer the @Inject annotations (abstract) for field injection in your Activity rather than calling the provision method from the Dagger Component (concrete).

Why? You will notice that the @Inject annotations are part of the javax.inject package. This is a standard for dependency injection APIs introduced into Java as part of JSR330. There are other DI frameworks, such as Guice and Toothpick, that can use these annotations. If you have to switch DI frameworks in the future it will be easier if you use the @Inject annotations. Yes, it does happen that you have to change DI frameworks. Roboguice is an example of a popular DI framework that has recently been retired.

To illustrate, let's take your Activity, add a few dependencies, and extract a method for injection like this:

public class MainActivity extends Activity { 
    @Inject SharedPreferences mSharedPrefs; 
    @Inject Foo foo;
    @Inject Bar bar;

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
    } 

    @VisibleForTesting
    protected void injectMembers() {
        ((DemoApplication) getApplication())
            .getComponent()
            .inject(this); 
    }
}

A wild (new) DI framework appears! You now have to change your app to use it quickly! You use JSR330. It's super-effective! (translation: you can re-use your JSR330 @Inject annotations because the new DI framework supports them). If you had changed to a new Guice-like framework, all you would need to do is rewrite your method:

    @VisibleForTesting
    protected void injectMembers() {
        GuiceLikeInjector.getInjector(this).injectMembers();  
    }

In contrast, if you had injected those fields manually using .getSharedPreferences(), getFoo() it's not very effective - you have to change a lot of lines of code.

David Rawson
  • 20,912
  • 7
  • 88
  • 124
  • Thanks for this. The reason I initially asked is because a new colleague was wanting to do some refactoring in the second option style. I was having a hard time convincing them option one was a better/more normal way, thanks for helping me build a better argument – im_not_josh Jan 27 '17 at 06:20
  • @i'm_not_josh I'm glad it helped. Even though the syntax may appear to be a little bit unusual, JSR330 is awesome! – David Rawson Jan 27 '17 at 06:54
1

If you look at the generated code of the Component, you'll notice that it implements the inject(MainActivity) method by setting injected fields directly using the activity reference you're passing it. So both options do the same thing.

I prefer the first approach for 2 main reasons. First it is a lot clearer that fields are injected when they are annotated as such, and second it keeps the code much cleaner. In the example you gave you inject a single field and it's harder to see the benefit, but I think it becomes much more apparent when you need to inject 10 fields, where you will have to assign all of them in the onCreate(), and declare getters for them in the component.

benji
  • 606
  • 6
  • 12