Search code examples
javadagger-2json-deserializationdagger

Dagger 2 - Provided object need instance from the target method


I need to provide TimeStamp to deserialize method of JsonDeserializer in dagger 2.

@Singleton
@Provides
public JsonDeserializer provideJsonDeserializer() {
        return new JsonDeserializer() {
            public Timestamp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                return new Timestamp(json.getAsJsonPrimitive().getAsLong());
            }
        };
    }

but that object needs JsonElement object from JsonDeserializer. How can i pass that obect to my provideTimestamp method.

@Singleton
@Provides
public Timestamp provideTimestamp() {


}

Or should I just not inject Timestamp through dagger, if that's the case can someone explain why, that might help me to learn more about dagger 2.


Solution

  • The way you have it in your first code sample is right, and how I'd have it: You should be calling new Timestamp(...), rather than deferring to Dagger.

    The distinction you should be drawing is injectables vs newables: Which objects in you graph should be injected, and which objects should be created with new? It can be tempting to think that every call to new is bad, because it reduces flexibility, but the reality is that it's more of a judgment call:

    • If you ever want to change or substitute the implementation, even via using test doubles or mocks in tests, then your object should be injectable. This is particularly true for nondeterministic or expensive objects, because you probably want your tests to be fast and deterministic.
    • If implementations are "value objects" that do little more than carry structured data around, then they are unlikely to be substituted and are probably newable.
    • If implementations have few dependencies and are easy to create, they might be newable. If implementations take a lot of services as constructor parameters (as opposed to many data values), then they should probably be injectable; this would be a good reason to use fakes or mocks in your tests.

    See also: To “new” or not to “new”…, by Miško Hevery


    As mentioned above, Timestamp is a value object: It is unlikely you'd ever want to substitute the implementation, Timestamp has no dependencies other than per-instance data, and implementations are very easy to create. This makes Timestamp an exceedingly good candidate to be a newable.

    In your case, you have an additional wrinkle: Your new timestamp is going to vary based on the json object you pass in, and the json object is going to vary enough that it's unlikely that you'd want to put it in your Dagger graph. This means that if you did want to pursue injection, rather than injecting a Timestamp, you would probably want to inject a TimestampFactory, which could be a constructor reference, an AutoFactory-created implementation, or any other implementation:

    interface TimestampFactory {
      Timestamp create(long value);
    }
    

    This seems excessive, but let's pretend instead you want Timestamp2, which also records the current time of day and therefore might make testing difficult. In that case, you could inject your timestamp factory:

    @Singleton
    @Provides
    public JsonDeserializer provideJsonDeserializer(Timestamp2Factory tsFactory) {
      return new JsonDeserializer() {
        public Timestamp2 deserialize(JsonElement json, Type typeOfT,
            JsonDeserializationContext context) throws JsonParseException {
          return tsFactory.create(json.getAsJsonPrimitive().getAsLong());
        }
      };
    }
    
    @Reusable
    @Provides
    public Timestamp2Factory provideTimestamp2Factory() {
      // Merge in other parameters and dependencies if needed,
      // but Timestamp2 only needs one value that create(long) supplies.
      return Timestamp2::new;
      // or, pre-Java-8:
      return new Timestamp2Factory() {
        @Override public Timestamp2 create(long time) {
          return new Timestamp2(time);
        }
      }
    }
    

    This would allow you to call provideJsonDeserializer in your tests and pass in a TimestampFactory of your choosing, such as one that uses a fake system time instead of a real one. This would allow your tests to be much safer and more deterministic.

    However, since you want the fast, deterministic value object Timestamp that doesn't have external dependencies like RPC services, file system access, or the actual system time, then keep your new Timestamp call as in your question.