AndroidEnthusiast
AndroidEnthusiast

Reputation: 6657

Lazy Injection with Dagger 2 on Android

I’m new to Dagger 2. I have this scenario, I wan't to inject an object across my app (in presenters, in api)

I do not have a way to provide it initially. It is not created till after authentication at some stage in my app.

From the documentation http://google.github.io/dagger/

I see Lazy loading might be a way to solve this e.g

@Inject 
Lazy<Grinder> lazyGrinder;

and then get the value like this using: lazyGrinder.get().grind();

My questions are:

Thanks

Upvotes: 17

Views: 29530

Answers (2)

rdshapiro
rdshapiro

Reputation: 766

If you aren't able to provide the object at the time of Component creation, don't add it to your Component graph! That is asking for confusing graph dependencies and inconsistency. A better solution to what you are considering is a @Subcomponent approach, which allows you to create a new component which inherits the dependencies from the parent, but also adds new one. Here's an example:

@Component
interface RegularComponent {
  @AppInstanceId String appInstanceId(); // unique per app install; not related to logging in
  AuthenticatedComponent newAuthenticatedComponent();
}

@Subcomponent
interface AuthenticatedComponent {
  Set<Friend> friends();
  @AccountId String accountId();
}

Here, the @AccountId in the subcomponent could use the appInstanceId to provide the account ID (if it needed to) since the Subcomponent shares dependencies with its parent component.

If you need to supply state to your modules for the subcomponent (with the accountId, auth token, etc) feel free to pass it in as a parameter to the @Module and store it in a private final field. You can read more on how to supply subcomponent modules in the documentation.

Upvotes: 6

Jeff Bowman
Jeff Bowman

Reputation: 95634

This isn't a good match for Lazy. Lazy is a great way to delay expensive object initialization, but it implies some semantics that you don't want or need, particularly regarding the "safely swap" behavior you want.

To put it simply, Lazy is a Provider wrapper that memoizes locally:

  • If you never call get, Dagger never creates the object in question.
  • The first call to get creates and stores the object instance.
  • The second call to get returns the same instance, and so on forever, regardless of whether the object was marked as Singleton.

This makes Lazy an excellent choice for an expensive object that would otherwise be a field (but may never be used). However, if the reference is likely to change (as your will), Lazy will simply be confusing: It will store the value at first use and never locally update, so multiple out-of-date copies might be floating around in your application regardless of what the "right" value is at any given time.


To borrow the use of Grinder from your example, better solutions include:

  • Using a @Provides method that returns a field in a Module, which can be updated later. You'll need to inject Provider<Grinder> for every long-lived object instance, because injected references to Grinder alone won't update. This still might be the best bet if you have a lot of short-lived objects.

    The reference is implicitly singleton, but is not annotated as such, because you're controlling the instance yourself. Dagger will call your getGrinder method frequently.

    @Module public class YourModule {
      private Grinder grinder;
    
      public void setGrinder(Grinder grinder) {
        this.grinder = grinder;
      }
    
      @Provides public Grinder getGrinder() {
        return grinder;
      }
    }
    
    /* elsewhere */
    YourModule module = new YourModule();
    YourComponent component = DaggerYourComponent.builder()
        .yourModule(module)
        .build();
    /* ... */
    module.setGrinder(latestAndGreatestGrinder);
    
  • As EpicPandaForce mentioned in the comments, create/bind a singleton GrinderHolder, GrinderController, or AtomicReference object that provides the current instance and allows for updating. That way it's impossible to inject a Grinder directly, but easy and obvious to inject the object that fetches the current correct Grinder. If your singleton GrinderHolder implementation doesn't create the Grinder until the first time you ask for it, then you have effectively created a Lazy singleton on your own.

Upvotes: 31

Related Questions