Reputation: 7442
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
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