Lazy Injection with Dagger 2 on Android
Asked Answered
A

2

17

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

Abernon answered 2/12, 2015 at 18:2 Comment(4)
What do you mean by override?Poohpooh
@Poohpooh i mean swap, question updatedAbernon
I used Lazy<T> only once. It gave me deadlocks on start-up. Never again. I have no idea what I did wrong, by the way. I personally would just create a singleton Holder object, as in GrinderHolder and set it in when you have it (null otherwise)Oldster
Dagger provider is lazy loading, which means as long as you do not use that object it would not initiate.Variation
H
31

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.

Hooknose answered 3/12, 2015 at 2:27 Comment(3)
Documentation here, with examples and explanations: google.github.io/dagger/api/2.0/dagger/Lazy.htmlHarman
But what if I want to instantiate the entire object graph at program startup but can build certain dependencies only at runtime? Is there a way to "add" those to the dependency container at runtime, so that I can lazy use them with Lazy<T>#get somewhere else? Or how would I else be able to use a dependency that is created at runtime around my program through dagger?Luetic
@cobby: This sounds like a new question, not a follow up, and I'd encourage you to ask at the top level with details (and possibly link this conversation here). I do think that injecting a Lazy or Provider is reasonable for you; it's the "safely swap" behavior that I was cautioning against, because a dependency injection framework isn't really good for getting a "latest" value that can change. Dagger is compile-time, so if you want to add values at runtime, you'll need to plumb them at compile time and only get them at runtime, or use a subcomponent (etc) created later.Hooknose
D
6

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.

Deneendenegation answered 3/12, 2015 at 15:21 Comment(1)
This is only possible if I create the subcomponent at the same place where the parent component is created (only then I can reuse the exact same runtime instances from the parent). What if I need the dependency from the parent component at a totally different place in my code (creating a newcomponent also gives me new instances)? I would somehow need a global container where I get this dependency from, which doesn't exist in Dagger? So, the question remains: How can you use a dependency that is created at runtime all around my program?Luetic

© 2022 - 2024 — McMap. All rights reserved.