Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

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

Thanks

like image 375
AndroidEnthusiast Avatar asked Dec 02 '15 18:12

AndroidEnthusiast


People also ask

What is the use of dagger 2 in Android?

Dagger 2 is a compile-time android dependency injection framework that uses Java Specification Request 330 and Annotations. Some of the basic annotations that are used in dagger 2 are: @Module This annotation is used over the class which is used to construct objects and provide the dependencies.

What is dagger injection Android?

Dagger is arguably the most used Dependency Injection, or DI, framework for Android. Many Android projects use Dagger to simplify building and providing dependencies across the app. It gives you the ability to create specific scopes, modules, and components, where each forms a piece of a puzzle: The dependency graph.

When would you use a lazy injection?

Lazy initialization is primarily used to improve performance, avoid wasteful computation, and reduce program memory requirements. These are the most common scenarios: When you have an object that is expensive to create, and the program might not use it.

What is Android dependency injection?

Dependency injection (DI) is a technique widely used in programming and well suited to Android development. By following the principles of DI, you lay the groundwork for good app architecture. Implementing dependency injection provides you with the following advantages: Reusability of code. Ease of refactoring.


2 Answers

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.

like image 57
Jeff Bowman Avatar answered Oct 12 '22 22:10

Jeff Bowman


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.

like image 6
rdshapiro Avatar answered Oct 12 '22 22:10

rdshapiro