如何使用complete = false从Dagger 1到Dagger 2迁移缺少的注入模块 [英] How to migrate missing inject from module with complete = false from Dagger 1 to Dagger 2

查看:209
本文介绍了如何使用complete = false从Dagger 1到Dagger 2迁移缺少的注入模块的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个图书馆项目/模块,由Android应用程序和常规的java应用程序使用。
在Dagger 1中,此项目/模块具有属性 complete = false 。在其中有一个@Inject字段不被任何类实现或@Provides方法所满足。这个想法是强制具有 complete = true 的顶部模块来提供系统特定的实现



仅仅是为了例子:在图书馆项目中,我有ActLogin活动,其字段 @Inject @Named(app version)mAppVersion 。登录到服务器时使用此字段的值。 ActLogin由使用此库的几个应用程序使用。每个应用程序的模块都有 complete = true 并提供价值与 @Provides @Named(app version)provideAppVersion()



迁移匕首2的文档( http://google.github.io/dagger/dagger-1-migration.html )说明:


Dagger 2模块都被声明为 complete = false 和library = true


并在同一时间主要文档页面( http://google.github.io/dagger/ )规定:


Dagger注释处理器是严格的,如果任何绑定无效或不完整,将导致编译器错误。 / p>

后者显然是正确的,因为当尝试使用不满足的注入错误生成(错误如果没有提供@Effect-或@Produces-annotated方法,则不能提供java.lang.String。



问题是:是可能迁移这种方法(推迟提供注入)到Dagger 2和如何?



PS最初我认为是一个脏的解决方法,在库的@Module中提供一些虚拟值,但是再次 - 你不能在Dagger 2中有模块覆盖(这是一种WTF(!!!)。模块覆盖是我最有用的功能创建单元测试时)。可能我错过了一些非常基本的东西,我希望有人可以指出: - )。

解决方案

事实证明,那里是这个的专用结构,但需要一些时间才能找到它。
如果您需要一个包含不满足注入的模块的组件 - 使其成为@Subcomponent。文档清楚地表明:


该关系允许子组件实现在声明时从其父级继承整个绑定图。因此,一个子组件在与父级关联之前不进行完整性评估


所以在我的情况下,我的图书馆项目需要成为一个匕首子组件。当我在我的应用程序项目中使用它时,我的应用程序匕首组件必须包含lib子组件。



在代码中:



图书馆子组件

  @Subcomponent(modules = Mod1.class)
public interface MyLibraryComponent {
void inject(Mod1Interface1 in);
}

应用程序组件:



pre $ @Component(modules = Mod2.class)
@Singleton
public interface MyAppComponent {
void inject(MainActivity act);
MyLibraryComponent newMyLibraryComponent();
}

请注意 MyLibraryComponent newMyLibraryComponent(); - 这是你如何告诉匕首你的组件包含该子组件。



图表实例化:

  MyAppComponent comp = DaggerMyAppComponent.builder()。build(); 

请注意,与使用组件组合与依赖关系(@ Component的属性)在这种情况下,您不必手动构造您的子组件。如果子组件的模块不需要特殊配置(即构造函数参数),则组件将自动照顾。如果一些子组件的模块需要配置,您可以通过组件实例化进行操作,如下所示:

  MyAppComponent comp = DaggerMyAppComponent.builder()。 
mod2(new Mod2SpecialConfiguration())。
build();

对于android,如果您的图书馆项目包含活动,则会有特殊的变化,因为每个活动必须单独注入按需与常规java应用程序相反,您通常在启动时注入整个应用程序。



为了示例,我们的库项目包含登录活动ActLogin,我们常用于多个应用程序。

  @Subcomponent(modules = Mod1.class)
public界面MyLibraryComponent {
void injectActLogin(ActLogin act);
void inject(Mod1Interface1 in);
}

问题是在Android中,我们通常会在Application对象中创建依赖图像这样:

  public class MyApplication extends Application {
private MyAppComponent mAppDependencyInjector;

@Override
public void onCreate(){
super.onCreate();
mAppDependencyInjector = DaggerMyAppComponent.builder()。build();
}

public MyAppComponent getAppDependencyInjector(){
return mAppDependencyInjector;
}
}

然后在您的活动中使用如下所示:

  @Override 
protected void onCreate(Bundle savedInstanceState){
// ...
((MyApplication)getApplication())。getAppDependencyInjector()。inject(this);
// ...
}

但我们的 ActLogin 活动是图书馆项目(和匕首组件)的一部分,不知道将要使用什么应用程序,我们将如何注入?



有一个很好的解决方案,但请注意,我不确定它是规范的(即在文档中没有提及,它不是作为一个例子给出当局(afaik))



项目的来源可以在github找到



首先,您必须在应用程序组件中扩展库匕首组件:

  public interface MyAppComponent扩展MyLibraryComponent {

这样你的应用程序组件将包含所有注入方法,所以你也可以注入它的活动。毕竟,顶级组件实际上是整个对象图(更确切地说,Dagger生成的DaggerMyAppComponent代表整个图形),因此它可以在所有子组件中注入本身定义的所有内容。



现在我们必须确保图书馆项目能够访问它。我们创建一个帮助类:

  public class MyLibDependencyInjectionHelper {
public static MyLibraryComponent getMyLibraryComponent(Application app){
if(app instanceof MyLibraryComponentProvider){
return((MyLibraryComponentProvider)app).getMyLibraryComponent();
} else {
throw new IllegalStateException(该应用程序未实现MyLibDependencyInjectionHelper.MyLibraryComponentProvider);
}
}


public interface MyLibraryComponentProvider {
MyLibraryComponent getMyLibraryComponent();
}
}

那么我们必须实现 MyLibraryComponentProvider 在我们的应用程序中类:

  public class MyApplication extends Application implements 
MyLibDependencyInjectionHelper.MyLibraryComponentProvider {
// ...

@Override
public MyLibraryComponent getMyLibraryComponent(){
return(MyLibraryComponent )mApp依赖性注射器;
}
}

并在ActLogin中注入:

  public class ActLogin extends Activity {
@Override
public void onCreate(Bundle savedInstanceState,PersistableBundle persistentState){
super.onCreate(savedInstanceState,persistentState);
// ...
MyLibDependencyInjectionHelper.getMyLibraryComponent(getApplication())。
injectActLogin(this);
// ...
}
}

有此解决方案的一个问题:如果您忘记在应用程序中实现 MyLibraryComponentProvider ,则在编译时不会出现错误,但在启动ActLogin活动时不会出现错误。幸运的是,可以通过简单的单元测试轻松避免。


I have a library project/module that is used by both Android apps and regular java apps. In Dagger 1 this project/module has property complete = false. Within there is an @Inject field that is not satisfied by any class implementation or @Provides method. The idea is to force the "top" module(s) which has complete = true to provide system specific implementation

Just for the sake of example: In the library project I have ActLogin activity that have field @Inject @Named("app version") mAppVersion. The value of this field is used when logging in into a server. ActLogin is used by several apps that use this library. Each app's module has complete = true and provides value with @Provides @Named("app version") provideAppVersion()

Documentation for migration of Dagger 2 (http://google.github.io/dagger/dagger-1-migration.html) states:

Dagger 2 modules are all declared as complete = false and library = true

and in the same time the "main" documentation page (http://google.github.io/dagger/) states:

The Dagger annotation processor is strict and will cause a compiler error if any bindings are invalid or incomplete.

The latter is obviously the correct one because when trying to build with unsatisfied inject error is produced (error: java.lang.String cannot be provided without an @Provides- or @Produces-annotated method).

The question is: is it possible to migrate this approach (deferring providing inject) to Dagger 2 and how?

P.S. Initially I thought as a dirty workaround to provide some dummy values in the library's @Module but then again - you cannot have module overrides in Dagger 2 (which is kind of WTF(!!!). Module overrides were the most useful feature for me when creating unit tests). Probably I am missing something very basic and I hope that someone can point it out :-).

解决方案

It turns out that there is dedicated construct for this but it takes some time to find it out. If you need to have a component that have a module which contains unsatisfied inject(s) - make it @Subcomponent. As documentation clearly states:

That relationship allows the subcomponent implementation to inherit the entire binding graph from its parent when it is declared. For that reason, a subcomponent isn't evaluated for completeness until it is associated with a parent

So in my case, my library project needs to be a dagger subcomponent. When I use it in my app project, my app dagger component have to include the lib subcomponent.

In code:

The library subcomponent:

@Subcomponent(modules = Mod1.class)
public interface MyLibraryComponent {
    void inject(Mod1Interface1 in);
}

The app component:

@Component(modules = Mod2.class)
@Singleton
public interface MyAppComponent {
    void inject(MainActivity act);
    MyLibraryComponent newMyLibraryComponent();
}

Please note the MyLibraryComponent newMyLibraryComponent(); - that is how you tell dagger that your component contains that subcomponent.

Graph instantiation:

    MyAppComponent comp = DaggerMyAppComponent.builder().build();

Please note that contrary to using component composition with dependencies (@Component's property) in this case you don't have to "manually" construct your subcomponent. The component will "automatically" take care for that in case the subcomponent's modules don't need special configuration (i.e. constructor parameters). In case some subcomponent's module requires configuration you do it trough the component instantiation like this:

MyAppComponent comp = DaggerMyAppComponent.builder().
                          mod2(new Mod2SpecialConfiguration()).
                          build();

For android there is a special twist if your library project contains activities because each activity have to be separately injected "on demand" contrary to regular java application where you usually inject the whole application once at start up time.

For the sake of example let's say our library project contains login activity "ActLogin" that we use as common for several applications.

@Subcomponent(modules = Mod1.class)
public interface MyLibraryComponent {
    void injectActLogin(ActLogin act);
    void inject(Mod1Interface1 in);
}

The problem is that in Android we usually create our dependency graph in the Application object like this:

public class MyApplication extends Application {
    private MyAppComponent mAppDependencyInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppDependencyInjector = DaggerMyAppComponent.builder().build();
    }

    public MyAppComponent getAppDependencyInjector() {
        return mAppDependencyInjector;
    }
}

and then in your activity you use it like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
    // ...
    ((MyApplication) getApplication()).getAppDependencyInjector().inject(this);
    // ...
}

but our ActLogin activity is part of the library project (and dagger component) that is not event aware of what application will it be used in so how are we going to inject it?

There is a nice solution but please note that I am not sure it is canonical (i.e. it is not mentioned in the documentation, it is not given as an example by the "authorities" (afaik))

Project's source can be found at github.

First you will have to extend the library dagger component in you app component:

public interface MyAppComponent extends MyLibraryComponent {

That way your app component will contain all the inject methods from the subcomponent so you will be able to inject it's activities too. After all, top component is in fact the whole object graph (more precisely the Dagger generated DaggerMyAppComponent represent the whole graph) so it is able to inject everything defined in itself + in all subcomponents.

Now we have to assure that the library project is able to access it. We create a helper class:

public class MyLibDependencyInjectionHelper {
    public static MyLibraryComponent getMyLibraryComponent(Application app) {
        if (app instanceof MyLibraryComponentProvider) {
            return ((MyLibraryComponentProvider) app).getMyLibraryComponent();
        } else {
            throw new IllegalStateException("The Application is not implementing MyLibDependencyInjectionHelper.MyLibraryComponentProvider");
        }
    }


    public interface MyLibraryComponentProvider {
        MyLibraryComponent getMyLibraryComponent();
    }
}

then we have to implement MyLibraryComponentProvider in our Application class:

public class MyApplication extends Application implements
    MyLibDependencyInjectionHelper.MyLibraryComponentProvider {
    // ...

    @Override
    public MyLibraryComponent getMyLibraryComponent() {
        return (MyLibraryComponent) mAppDependencyInjector;
    }
}

and in ActLogin we inject:

public class ActLogin extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        // ...
        MyLibDependencyInjectionHelper.getMyLibraryComponent(getApplication()).
                           injectActLogin(this);
        // ...
    }
}

There is a problem with this solution: If you forget to implement the MyLibraryComponentProvider in your application you will not get an error at compile time but at runtime when you start ActLogin activity. Luckily that can be easily avoided with simple unit test.

这篇关于如何使用complete = false从Dagger 1到Dagger 2迁移缺少的注入模块的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆