Android 匕首2:从组件或子组件注入类

Android 匕首2:从组件或子组件注入类,android,dependency-injection,dagger-2,Android,Dependency Injection,Dagger 2,我的应用程序中有两个不同的dagger组件:MainComponent和UserComponentUserComponent是MainComponent的子组件,@UserScope仅在用户登录时存在 当我需要注入一个只能在用户登录或注销时存在的类时,它工作正常。但有两种情况根本不起作用: 有些类可以在用户登录时存在,也可以不存在。例如,这可以是一个片段,显示商品列表和可选的“购买”按钮,只有在用户登录时才可见。或者推送通知服务-推送通知可能会到达,无论用户是否登录,并且您可能需要为不同的用户以

我的应用程序中有两个不同的dagger组件:
MainComponent
UserComponent
UserComponent
MainComponent
的子组件,
@UserScope
仅在用户登录时存在

当我需要注入一个只能在用户登录或注销时存在的类时,它工作正常。但有两种情况根本不起作用:

有些类可以在用户登录时存在,也可以不存在。例如,这可以是一个片段,显示商品列表和可选的“购买”按钮,只有在用户登录时才可见。或者推送通知服务-推送通知可能会到达,无论用户是否登录,并且您可能需要为不同的用户以不同的方式处理它们这是一种在初始化过程中必须向类注入不同组件的情况。

还有一些类可以比
@UserScope
更长寿。例如,带有侧菜单的活动。侧菜单在顶部显示用户头像和名称,但在底部也包含“退出”按钮。当用户按下退出按钮时,“活动”应更改菜单标题当类创建后必须重新注入不同的组件时,就会出现这种情况。

我如何处理这种情况? 想到的唯一可能的解决方案是在用户注销时使用null user创建
UserComponent
,然后从模块中提供的所有
@方法返回null,但这需要大量工作。此外,我希望
UserComponent
在用户注销时为null,而不是让它的版本失效

我真的很喜欢这样的东西:

if (Injector.userComponent() != null) {
    Injector.userComponent().inject(this);
} else {
    Injector.mainComponent().inject(this);
}
但此代码给出错误:
IUser不能在没有@Provides注释方法的情况下提供。
,因为MainComponent不提供IUser。但它真的不应该

难道没有办法告诉dagger只留下它找不到null的字段吗?

以下是我目前的执行情况:

@Singleton
@Component(modules = {
        ApplicationModule.class,
        AuthorizationModule.class
})
public interface MainComponent {

    void inject(MyClass item);

    ...

    UserComponent.Builder userComponentBuilder();

}

@Scope
public @interface UserScope {

}

@UserScope
@Subcomponent(modules = {
        UserModule.class
})
public interface UserComponent {

    void inject(MyClass item);

    ...

    @Subcomponent.Builder
    interface Builder {
        Builder userModule(UserModule module);
        UserComponent build();
    }

}

@Module
public class UserModule {

    private User mUser;

    public UserModule(User user) {
        mUser = user;
    }

    @Provides
    @UserScope
    IUser user() {
        return mUser;
    }

}

public class Injector {

    private static MainComponent sMainComponent;
    private static UserComponent sUserComponent;

    public static void createMainComponent(MainApplication application) {
        sMainComponent = DaggerMainComponent.builder()
                .applicationModule(new ApplicationModule(application))
                .build();
    }

    @NonNull
    public static MainComponent mainComponent() {
        return sMainComponent;
    }

    public static void createUserComponent(User user) {
        if (user == null) {
            throw new NullPointerException("Cannot create user component without a user");
        }

        sUserComponent = sMainComponent.userComponentBuilder()
                .userModule(new UserModule(user))
                .build();
    }

    public static void destroyUserComponent() {
        sUserComponent = null;
    }

    @Nullable
    public static UserComponent userComponent() {
        return sUserComponent;
    }

}   

public class MyClass {

    // Provided by MainComponent
    @Inject
    IAuthorizationManager mAuthorizationManager;
    // Provided by UserComponent
    @Inject
    IUser mUser;

    public MyClass() {

        // Works, but not if userComponent is currently null (user is not logged in):
        Injector.userComponent().inject(this);

        // Doesn't work, but I'd prefer something like that instead of creating invalid UserComponent:
        if (Injector.userComponent() != null) {
            Injector.userComponent().inject(this);
        } else {
            Injector.mainComponent().inject(this);
        }

    }

}

最后,我决定让UserModule提供的依赖项可以为null,同时使UserComponent为非null

这意味着,无论在何处使用UserModule中的依赖项,都必须将其声明为可空。这通常是误导性的,因为若类是由UserModule创建的,并且仅当用户存在时才存在,那个么它内部的依赖项肯定是非空的,但您不能强制这样做

附加修改后的示例代码,以防有人感兴趣

@Singleton
@Component(modules = {
        ApplicationModule.class,
        AuthorizationModule.class
})
public interface MainComponent {

    void inject(MyClass item);

    ...

    UserComponent.Builder userComponentBuilder();

}

@Scope
public @interface UserScope {

}

@UserScope
@Subcomponent(modules = {
        UserModule.class
})
public interface UserComponent {

    void inject(MyClass item);

    ...

    @Subcomponent.Builder
    interface Builder {
        Builder userModule(UserModule module);
        UserComponent build();
    }

}

@Module
public class UserModule {

    @Nullable
    private User mUser;

    public UserModule(@Nullable User user) {
        mUser = user;
    }

    @Provides
    @Nullable
    @UserScope
    IUser user() {
        return mUser;
    }

}

public class Injector {

    private static MainComponent sMainComponent;
    private static UserComponent sUserComponent;

    public static void createMainComponent(MainApplication application) {
        sMainComponent = DaggerMainComponent.builder()
                .applicationModule(new ApplicationModule(application))
                .build();
    }

    @NonNull
    public static MainComponent mainComponent() {
        return sMainComponent;
    }

    public static void createUserComponent(@Nullable User user) {
        sUserComponent = sMainComponent.userComponentBuilder()
                .userModule(new UserModule(user))
                .build();
    }

    @NonNull
    public static UserComponent userComponent() {
        return sUserComponent;
    }

}   

public class MyClass {

    @Inject
    IAuthorizationManager mAuthorizationManager;

    @Inject
    @Nullable
    IUser mUser;

    public MyClass() {
        Injector.userComponent().inject(this);
    }

}

我不确定如何在UserComponent中以用户身份返回null将是一项艰巨的工作。我认为这就像创建一个带有null user的UserModule一样简单。你能解释一下你的意思吗?@tompee是的-它正在用null user创建UserModule。但由于UserModule现在将能够返回null依赖项,您需要开始在Inject旁边用null-able标记所有依赖项(否则它的编译错误)。现在最糟糕的部分不是它需要大量的工作-它不需要太多的代码-最糟糕的部分是许多依赖项将被标记为可空,即使它们实际上不能为空。如果只有当用户存在时才存在两个依赖项,并且其中一个依赖项注入另一个依赖项,则必须将注入项标记为可空,但事实并非如此。一种方法是删除用户注入,并将其从组件中公开。这样,您就不会被迫在注射过程中提供它。另外,注入是同步的,所以您可以在注入之后立即执行用户实例请求。