Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
202 views
in Technique[技术] by (71.8m points)

dagger 2 - Dagger2 and Android

I am trying to implement Dagger Dependency Injection into my app but I am having a hard time understanding how it works, especially coming from Spring where DI was much easier and much more declarative.

What I want to do is have a bunch of inject-ready objects that can be used throughout my app, that is the SharedPreferences, the Network objects (OkHttp, Retrofit, Picasso...), and EventBus and a SchedulerProvider object for RxJava.

This sample seems to offer everything I need but I am having trouble grasping some concepts.

In this other sample referenced in the previous page they create a GithubService that uses the Retrofit object provided in the NetModule. For that they create a GithubComponent like this:

@UserScope
@Component(dependencies = NetComponent.class, modules = GitHubModule.class)
public interface GitHubComponent {
    void inject(MainActivity activity);
}

They are using a UserScope annotation that defines its own scope. Since @Singleton cannot be used, does this mean that the object will not be a Singleton? How do scopes really affect the DI? It seems they are only declaring a named-scope with no more effect, but I'm not sure.

Also, my app is built using Activities with Fragments. Do I have to create a Component for every Fragment in my app? i.e. I need to use my REST api services all throughout the app, do I have to declare a Component for every screen using them? This raises the amount of boilerplate code required and thus sounds not very clean.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Components ought to be the "big DI provider" that provides everything for a specific scope.

For example, you could have a SingletonComponent with @Singleton scope that has every single module added to it that has at least one @Singleton scoped provider method.

@Singleton
@Component(modules={NetworkingModule.class, DatabaseModule.class, MapperModule.class, UtilsModule.class})
public interface SingletonComponent {
    // provision methods
    OkHttpClient okHttpClient();
    RealmHolder realmHolder();
    // etc.
}

You can have the provision methods defined per module.

public interface DatabaseComponent {
    RealmHolder realmHolder();
}

public interface NetworkingComponent{
    OkHttpClient okHttpClient();
}

In which case you'd have

@Singleton
@Component(modules={NetworkingModule.class, DatabaseModule.class, MapperModule.class, UtilsModule.class})
public interface SingletonComponent 
      extends NetworkingComponent, DatabaseComponent, MapperComponent, UtilsComponent {
    // provision methods inherited
}


In a Module, you can specify a factory method ("provider method") that specifies how to create a particular type of dependency.

For example,

@Module
public class NetworkingModule {
    @Provides
    @Singleton
    OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()./*...*/.build();
    }

    @Provides
    @Singleton
    Retrofit retrofit(OkHttpClient okHttpClient) {
        // ...
    }
}

You can imagine the @Singleton scope as the big DI container that Spring would have given you.


You can also provide instances of the class using @Inject annotated constructor. This can receive any class from the component that is able to instantiate it from provider methods within that scoped component's modules (and unscoped dependencies, of course).

@Singleton
public class MyMapper {
    @Inject
    public MyMapper(RealmHolder realmHolder, OkHttpClient okHttpClient) { // totally random constructor for demo
    }
}

or

@Singleton
public class MyMapper {
    @Inject
    RealmHolder realmHolder;

    @Inject
    OkHttpClient okHttpClient;

    @Inject
    public MyMapper() {
    }
}

Then this will be available in the component, you can even make provision method for it to make it inheritable in component dependencies:

@Singleton 
@Component(modules={...})
public interface SingletonComponent {
    MyMapper myMapper();
}


With Dagger2, you can also additionally create "subscoped components", that inherit all dependencies provided from a component of a given scope.

For example, you can inherit all @Singleton scoped components, but you can still have new scoped dependencies per that new scope, such as @ActivityScope.

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

Then, you can create subscoped components using either subcomponents, or component dependencies.


  • Subcomponent:

.

@ActivityScope
@Subcomponent(modules={MainActivityModule.class})
public interface MainActivityComponent {
    MainPresenter mainPresenter();
}

Then this can be created in its parent scoped component:

@Singleton 
@Component(modules={...})
public interface SingletonComponent {
    MainActivityComponent mainActivityComponent(MainActivityModule module);
}

Then you can use the singleton component to instantiate this:

SingletonComponent singletonComponent = DaggerSingletonComponent.create();
MainActivityComponent mainActivityComponent = singletonComponent.mainActivityComponent(new MainActivityModule(mainActivityHolder));

  • Component dependency:

.

@ActivityScope
@Component(dependencies={SingletonComponent.class}, modules={MainActivityModule.class})
public interface MainActivityComponent extends SingletonComponent {
    MainPresenter mainPresenter();
}

For this to work, you must specify provision methods in the superscoped component.

Then you can instantiate this like so:

SingletonComponent singletonComponent = DaggerSingletonComponent.create();
MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
                       .singletonComponent(singletonComponent)
                       .mainActivityModule(new MainActivityModule(mainActivityHolder))
                       .build();


In Dagger2, you can therefore obtain dependencies either via:

  • @Inject annotated constructor parameters
  • @Inject annotated fields on classes with @Inject annotated constructor
  • from @Component provision methods
  • via manual field injection method defined in component (for classes you can't create using @Inject annotated constructor)

Manual field injection can happen for classes like MainActivity, that you yourself don't create.

Manual field injection injects only the specific class that you are injecting. Base-classes don't get automatically injected, they need to call .inject(this) on component.

It works like this:

@ActivityScope
@Subcomponent(modules={MainActivityModule.class})
public interface MainActivityComponent {
    void inject(MainActivity mainActivity);
}

Then you can do:

public class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
                          .singletonComponent(getSingletonComponent())
                          .mainActivityModule(new MainActivityModule(this))
                          .build(); // ensure activity `holder` instead, and retain component in retained fragment or `non-configuration instance`
        mainActivityComponent.inject(this);       
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...