Andrea Richiardi
Andrea Richiardi

Reputation: 733

Dagger and dependecies on provided classes

I am "Daggering" my Android app and I am facing a little problem that I don't know if it's me or framework's fault. If it's me I will be very disappointed by myself :)

I have the following class to provide:

@Singleton
public class XmlService {

    private final DataCommitter<XmlWritable> mXmlCommitter;
    private final DataReader<XmlPushable> mXmlReader;
    private final ConcurrentObjectMonitor mConcObjMonitor;

   @Inject
   public XmlService(DataCommitter<XmlWritable> xmlCommitter,
        DataReader<XmlPushable> xmlProvider,
        ConcurrentObjectMonitor concObjMonitor) {
    mXmlCommitter = xmlCommitter;
    mXmlReader = xmlProvider;
    mConcObjMonitor = concObjMonitor;
   }

With the following (among the others) class:

public class XmlDataReader implements DataReader<XmlPushable> {
  // No Singleton no more.
  // Eager Singleton (Therefore it should be made thread safe)
  //static XmlDataReader mInstance = new XmlDataReader();
  [CUT]

  protected XmlPullParser mXmlPullParser;

  @Inject
  public void XmlDataRead(XmlPullParser xmlPullParser) {
      mXmlPullParser = xmlPullParser;
  }

This class is referred in my XmlServiceModule like this:

@Module(complete = true)
public class XmlServiceModule {

    @Provides DataReader<XmlPushable> provideDataReader(XmlDataReader dataReader) {
        return dataReader;
    }
}

Now, the question is, is it legal to Inject provided classes? Because I am getting an error with the @Inject on the XmlDataReader ctor: Cannot inject public void XmlDataRead(org.xmlpull.v1.XmlPullParser).


EDIT: sorry guys, there was an error in my sample code, but I will leave it as is, because it can be useful in order to understand Christian's answer below.

Upvotes: 1

Views: 1577

Answers (1)

Christian Gruber
Christian Gruber

Reputation: 4761

Actually, you have an error in your sample code. You have no injectable constructor - you cannot inject an instance method, only a constructor, and that method is never called.

You should have:

public class XmlDataReader implements DataReader<XmlPushable> {

  protected final XmlPullParser mXmlPullParser;

  @Inject
  public XmlDataReader(XmlPullParser xmlPullParser) {
      mXmlPullParser = xmlPullParser;
  }
}

This way it's a constructor, and will be properly injected.

As to the overall approach, you're in the right direction. You @Provide DataReader<XmlPushable, and effectively bind XmlDataReader to it in your provides method. So far so good. Dagger, when asked for DataReader<XmlPushable> will use that binding, and will effectively pass-through the dependency and provide XmlDataReader to satisfy this.

XmlDataReader has an @Inject annotated constructor, so Dagger's analysis will see it as an "injectable type" so you don't need an @Provides to provide it. As long as you either have an XmlPullParser that either has an @Inject annotated constructor or is provided in an @Provides method on a module, what you've coded here seems good, but is not quite complete.

You don't have any entryPoints listed in your @Module, and you must have a class that will be the first thing you obtain from the ObjectGraph. So this code is reasonable as far as it goes, but incomplete. You need something that injects (through a field or constructor) DataReader<XmlPushable> For example, you might make a FooActivity which does this.

Consider:

public class XmlDataReader implements DataReader<XmlPushable> {    
  protected XmlPullParser mXmlPullParser;

  @Inject public XmlDataReader(XmlPullParser xmlPullParser) {
    mXmlPullParser = xmlPullParser;
  }
}

@Module(entryPoints = { FooActivity.class, BarActivity.class })
public class XmlServiceModule {
  @Provides DataReader<XmlPushable> provideDataReader(XmlDataReader dataReader) {
    return dataReader;
  }

  @Singleton
  @Provides XmlPullParserFactory parserFactory() {
     XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
     factory.setNamespaceAware(true);
     return factory;
  }

  @Provides XmlPullParser parser(XmlPullParserFactory factory) {
     XmlPullParser xpp = factory.newPullParser();
  }
}

public class YourApp extends Application {
  private ObjectGraph graph;

  @Override public void onCreate() {
    super.onCreate();
    graph = ObjectGraph.get(new ExampleModule(this));
  }

  public ObjectGraph graph() {
    return objectGraph;
  }
}

public abstract class BaseActivity extends Activity {
  @Override protected void onCreate(Bundle state) {
    super.onCreate(state);
    ((YourApp) getApplication()).objectGraph().inject(this);
  }
}

class FooActivity extends BaseActivity {
  @Inject DataReader<XmlPushable> reader;

  @Override public void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    // custom Foo setup
  }
}

class BarActivity extends BaseActivity {
  @Inject DataReader<XmlPushable> reader;

  @Override public void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    // custom Bar setup
  }
}

This is probably close to what you want. All things are reachable from an entry point, and all things you depend on are bound. The two Activities are your "entry points" (roots in the object graph). When they are initialized, they ultimately call the ObjectGraph's inject(Object) method on themselves, which causes Dagger to inject any @Inject annotated fields. Those fields are the DataReader<XmlPushable> fields, which is satisfy by XmlDataReader, which is provided with an XmlPullParser, which is provided by using a XmlPullParser. It all flows down the dependency chain.

Small note - @Module(complete=true) is the default. You don't need to specify complete, unless it's incomplete and you specify false

Upvotes: 3

Related Questions