Alexey
Alexey

Reputation: 7442

Dagger 2: Inject class from component or subcomponent

I have two different dagger components in my app: MainComponent and UserComponent. UserComponent is a subcomponent of MainComponent and @UserScope exists only as long as user is logged in.

It works ok when I need to inject a class that can only exist when user is logged in or logged out. But there are 2 cases that doesn't work well at all:

Some classes can exists both when user is logged in and not. For instance, this can be a fragment displaying a list of goods and optional "buy" button that is only visible when the user is logged in. Or a push notification service - push notifications may arrive regardless of whether user is logged in and you may need to handle them differently for different users. This is a case when class has to injected with a different component during initialization.

There are also classes that can outlive @UserScope. For example an activity with side menu. Side menu displays user avatar and name at the top, but it also contains "exit" button at the bottom. When user presses exit, activity should change menu header. This is a case when class has to be re-injected with different component after it was created.

How do I handle this situations? The only possible solution that comes to mind is to create UserComponent with null user when user is logged out and then return null from all @Provides methods in modules, but that's a lot of work. Furthermore, I'd like like UserComponent to be null when user is logged out instead of having it's defunct version.

I'd really like something like:

if (Injector.userComponent() != null) {
    Injector.userComponent().inject(this);
} else {
    Injector.mainComponent().inject(this);
}

But this code gives error: IUser cannot be provided without an @Provides-annotated method. because MainComponent doesn't provide IUser. But it really shouldn't.

Isn't there a way to tell dagger to just leave fields that it can't find null?

Below is my current implementation:

@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);
        }

    }

}

Upvotes: 0

Views: 999

Answers (1)

Alexey
Alexey

Reputation: 7442

In the end I've decided to just let dependencies provided by UserModule be Nullable, while making UserComponent NonNull.

This means that anywhere you use dependency from UserModule, you have to declare it as Nullable. That is often misleading because if class is created by UserModule and exists only when user exists, dependencies inside it are definitely NonNull, but you can't force that.

Attaching modified example code in case anyone is interested.

@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);
    }

}

Upvotes: 0

Related Questions