Lazy Injection with Dagger 2 on Android

AndroidEnthusiast picture AndroidEnthusiast · Dec 2, 2015 · Viewed 18.6k times · Source

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:

  • Can I safely swap the object after this with a new one?
  • Are there any other recommended ways to do this?

Thanks

Answer

Jeff Bowman picture Jeff Bowman · Dec 3, 2015

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.