29

So, I'm currently redesigning an Android app of mine to use Dagger. My app is large and complicated, and I recently came across the following scenario:

Object A requires a special DebugLogger instance which is a perfect candidate for injection. Instead of passing around the logger I can just inject it through A's constructor. This looks something like this:

class A
{
    private DebugLogger logger;

    @Inject
    public A(DebugLogger logger)
    {
        this.logger = logger;
    }

    // Additional methods of A follow, etc.
}

So far this makes sense. However, A needs to be constructed by another class B. Multiple instances of A must be constructed, so following Dagger's way of doing things, I simple inject a Provider<A> into B:

class B
{
    private Provider<A> aFactory;

    @Inject
    public B(Provider<A> aFactory)
    {
        this.aFactory = aFactory;
    }
}

Ok, good so far. But wait, suddenly A needs additional inputs, such as an integer called "amount" that is vital to its construction. Now, my constructor for A needs to look like this:

@Inject
public A(DebugLogger logger, int amount)
{
...
}

Suddenly this new parameter interferes with injection. Moreover, even if this did work, there would be no way for me to pass in "amount" when retrieving a new instance from the provider, unless I am mistaken. There's several things I could do here, and my question is which one is the best?

I could refactor A by adding a setAmount() method that is expected to be called after the constructor. This is ugly, however, because it forces me to delay construction of A until "amount" has been filled in. If I had two such parameters, "amount" and "frequency", then I would have two setters, which would mean either complicated checking to ensure that construction of A resumes after both setters are called, or I would have to add yet a third method into the mix, like so:

(Somewhere in B):

A inst = aFactory.get();
inst.setAmount(5);
inst.setFrequency(7);
inst.doConstructionThatRequiresAmountAndFrequency();

The other alternative is that I don't use constructor-based injection and go with field-based injection. But now, I have to make my fields public. This doesn't sit well with me, because now I am obligated to reveal internal data of my classes to other classes.

So far, the only somewhat elegant solution I can think of is to use field-based injection for providers, like so:

class A
{
    @Inject
    public Provider<DebugLogger> loggerProvider;
    private DebugLogger logger;

    public A(int amount, int frequency)
    {
        logger = loggerProvider.get();
        // Do fancy things with amount and frequency here
        ...
    }
}

Even still, I'm unsure about the timing, since I'm not sure if Dagger will inject the provider before my constructor is called.

Is there a better way? Am I just missing something about how Dagger works?

Frank Schmitt
  • 30,195
  • 12
  • 73
  • 107
Alex
  • 1,103
  • 1
  • 11
  • 24

6 Answers6

55

What you are talking about is known as assisted injection and is not currently supported by Dagger in any automatic fashion.

You can work around this with the factory pattern:

class AFactory {
  @Inject DebugLogger debuggLogger;

  public A create(int amount, int frequency) {
    return new A(debuggLogger, amount);
  }
}

Now you can inject this factory and use it to create instances of A:

class B {
  @Inject AFactory aFactory;

  //...
}

and when you need to create an A with your 'amount' and 'frequency' you use the factory.

A a = aFactory.create(amount, frequency);

This allows for A to have final instances of the logger, amount, and frequency fields while still using injection to provide the logger instance.

Guice has an assisted injection plugin which essentially automates the creation of these factories for you. There have been discussion on the Dagger mailing list about the appropriate way for them to be added but nothing has been decided upon as of this writing.

hsz
  • 148,279
  • 62
  • 259
  • 315
Jake Wharton
  • 75,598
  • 23
  • 223
  • 230
  • Thank you for your quick response. The factory pattern you've described seems like the best approach. I've also realized though that if Dagger supported private field injection it would easily allow what I'm trying to accomplish. I wonder why that wasn't part of Dagger's original design? I assume Dagger injects using reflection. If so then private fields should pose no issue, right? – Alex Apr 16 '13 at 15:45
  • Dagger falls back to reflection but that is not its primary means of injection. It generates code which directly sets the fields or calls your constructors. As such it works like any other piece of code in your source tree and cannot access `private` members. – Jake Wharton Apr 16 '13 at 16:46
  • And some security managers will break reflection that relies on changing accessibility in your classes. – Christian Gruber Apr 16 '13 at 23:15
  • Luckily I've never had to deal with any rumored issues that arise around the use of reflection. I've heard about the evil security managers. For Android it doesn't seem like an issue. My app is sitting at around 120k users with daily crash reporting and I haven't witnessed a security manager block reflection yet on any of the users' phones (if that were to happen, my app would be useless, since it performs vital REST/JSON deserialization via reflection). But I can see how having generated code is much cleaner and offers compile-time checking. – Alex Apr 17 '13 at 15:15
  • I'm fairly certain Dagger was written because of the performance hit caused by the use of reflection (amongst other reasons). Dagger flexes it's muscles during compilation and generates code (as much as possible) to avoid reflection. – Christopher Perry Nov 14 '13 at 15:24
  • Did you forget to use frequency in your factory? Or am I missing something? – maracuja-juice Feb 28 '18 at 21:36
3

What Jake's post says is perfectly true. That said, we (some of the Google folk who work with Guice and Dagger) are working on an alternative version of "assisted injection" or automatic-factory generation which should be usable by Guice or Dagger or stand-alone - that is, it will generate factory class source code for you. These factory classes will (if appropriate) be injectable as any standard JSR-330 class would. But it is not yet released.

Pending a solution like this, Jake Wharton's approach is advisable.

Christian Gruber
  • 4,691
  • 1
  • 28
  • 28
  • Yeah no worries, I realize Dagger is still in its early versions. I do like that some of these other features are being developed as plugins to keep the overall size of the binary down (which is, for me at least, what makes it such an attractive option for Android development). – Alex Apr 17 '13 at 15:07
  • 1
    So mentioned auto-factory generation: https://github.com/google/auto/tree/master/factory – phazei Feb 18 '15 at 02:53
3

You're having a problem because you are mixing injectables and non injectables in your constructor. The general rules for injection that will save you tons of heartache and keep your code clean are:

  1. Injectables can ask for other injectables in their constructor, but not for newables.

  2. Newables can ask for other newables in their constructor but not for injectables.

Injectables are service type objects, ie objects that do work such as a CreditCardProcessor, MusicPlayer, etc.

Newables are value type objects such as CreditCard, Song, etc.

Christopher Perry
  • 38,891
  • 43
  • 145
  • 187
2

Jake's post is great, but there is more simple way. Google created AutoFactory library for creating factory automatically at compile time.

First, create class A with @AutoFactory annotation and @Provided annotation for injecting arguments:

@AutoFactory
public class A {

    private DebugLogger logger;

    public A(@Provided DebugLogger logger, int amount, int frequency) {
        this.logger = logger;
    }
}

Then library creates AFactory class at compile time. So you need just inject the factory to a constructor of B class.

public class B {

    private final AFactory aFactory;

    @Inject
    public B(AFactory aFactory) {
        this.aFactory = aFactory;
    }

    public A createA(int amount, int frequency) {
        return aFactory.create(amount, frequency);
    }
}
Sergei Vasilenko
  • 2,313
  • 2
  • 27
  • 38
0

I just want to add that years passed after this question has been posted and now there is a library called AssistedInject which has been created by Jake and friends at Square, to solve the exact same problem and is fully compatible with Dagger 2.

You can find it here: https://github.com/square/AssistedInject

MatPag
  • 41,742
  • 14
  • 105
  • 114
0

Dagger 2 now has support for assisted injection which should help solve your usecase.

https://dagger.dev/dev-guide/assisted-injection

You can wrap your class like this:

@AssistedInject
public A(DebugLogger logger, @Assisted int amount)
{
...
}

Create a factory for this class.

@AssistedFactory
public interface MyDataFactory {
  A create(int amount);
}

and in your client, you can use:

@Inject MyDataFactory dataFactory;

  void setupA(int amount) {
    A a = dataFactory.create(config);
    // ...

  }

shubhamgarg1
  • 1,619
  • 1
  • 15
  • 20