Dagger and dependecies on provided classes - android

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.

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

Related

Mocking dependency not listed in module

I am using very simple and likely very common scenario. Here is my sample dependency:
public class MyDependency {
#Inject
public MyDependency(...) {
...
}
}
I am not listing the above in any module (that is, there is no #Provides for MyDependency).
My sample use case goes like this:
public class ThePresenter {
#Inject
MyDependency myDependency;
public ThePresenter() {
App.getInstance().getAppComponent().inject(this);
}
}
Now I'd like to mock my dependency in unit tests. I don't want to use modules overrides (that would mean I have to add #Provides for all my dependencies marked with #Inject constructors), test components etc. Is there any alternative but standard and simple approach for the problem?
You need to use constructor injection, rather than your injection site inside the Presenter class constructor. Expose your Presenter to dagger2 by adding the #Inject annotation on the constructor (like you have done with the dependency):
public class ThePresenter {
private final MyDependency myDependency;
#Inject public ThePresenter(MyDependency myDependency) {
this.myDependency = myDependency;
}
}
This then allows inversion of control and supplying the dependency/mock.
Usage :
public class ThePresenterTest {
#Mock private MyDependency myDependency;
private ThePresenter presenter;
#Before public void setup() {
MocktioAnnotations.initMocks(this);
presenter = new ThePresenter(myDependency);
Mockito.when(myDependency.someMethod()).thenReturn(someValue);
....
}
}
Just mock it?
public class ThePresenterTest {
#Mock MyDependency myDependency;
private ThePresenter presenter;
#Before
public void setup() {
initMocks(this);
presenter = new ThePresenter();
}
}

Cant inject classes using Dagger on Android

I am beggining with Dagger, I am using 1.2 version of it, and I have the following scenario:
Module:
#Module(injects = {
AuthenticationService.class
})
public class ServiceModule {
#Provides
AuthenticationService provideAuthenticationService() {
return ServiceFactory.buildService(AuthenticationService.class);
}
}
On my Application class I create the ObjectGraph:
public class FoxyRastreabilidadeApplication extends Application {
private static FoxyRastreabilidadeApplication singleton;
#Override
public void onCreate() {
super.onCreate();
createObjectGraph();
singleton = this;
}
private void createObjectGraph() {
ObjectGraph.create(ServiceModule.class);
}
}
and finally, at my LoginActivity, I try to inject my AuthenticationService:
public class LoginActivity extends Activity implements LoaderCallbacks<Cursor> {
private UserLoginTask mAuthTask = null;
#Inject
AuthenticationService authenticationService;
}
At this point, when I try to access my AuthenticationService instance it is always null, meaning it wasnt injected at all, I debugged my provider method to be sure of it, so, the question is, am I missing something? If so, what is it?
You need to hold on to the ObjectGraph and ask it to inject your objects directly.
Add an instance variable to your custom Application subclass to hold on to the created ObjectGraph:
this.objectGraph = ObjectGraph.create(ServiceModule.class);
Add a convenience method to your Application to do injection:
public void inject(Object object) {
this.objectGraph.inject(object);
}
Call that method wherever you need injection to happen, for example in your Activity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication)getApplication()).inject(this);
...
}

Dagger - nested injections, is it necessary to call inject()?

I'm new to Dagger and at the begininig I face some issues. I have simple structure so far in my project. My injection module:
#Module(
injects = {GameBoardFragment.class, GameManager.class},
complete = false,
library = true
)
public class GameObjectsProviderModule {
private final Application mApplication;
public GameObjectsProviderModule(Application application){
this.mApplication = application;
}
#Provides
#Singleton
public GameManager provideGameManager(){
return new GameManager();
}
#Provides
public Board getBoard(){
return new Board();
}
#Provides #Singleton #ForApplication Context provideAppContext() {
return mApplication;
}
My simplified custom app class looks like that:
public class MyApp extends Application {
private static ObjectGraph mApplicationGraph;
#Override public void onCreate() {
super.onCreate();
mApplicationGraph = ObjectGraph.create(new GameObjectsProviderModule(this));
}
public static ObjectGraph getObjectGraph(){
return mApplicationGraph;
}
}
And now, my fragment looks like that:
public class GameBoardFragment extends Fragment {
#Inject
GameManager mGameManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
MyApp.getObjectGraph().inject(this);
View root = inflater.inflate(R.layout.fragment_game_board, container, false);
findViews(root);
confViews();
return root;
}
}
And finally my GameManager class
public class GameManager {
#Inject Board mBoard;
public GameManager(){
MyApp.getObjectGraph().inject(this);
}
}
Andy hey, it works! Great. But my question is why it doesn't work in case I comment out this line:
MyApp.getObjectGraph().inject(this);
Do we have always explicitly call inject() function to make all necessary injections take place event in nested objects?
It looks not, as shows coffe maker example:
https://github.com/square/dagger/tree/master/examples/simple/src/main/java/coffee
Why then I have to call inject() in GameManager class to get it working?
Edit:
The consturctor injection approach works just fine.
But for future use I tried to get field injection running, and so far I haven't succeed.
I commented out both #Provide methods from module and I made my GameManager look like this:
#Singleton
public class GameManager {
#Inject Board mBoard;
#Inject
public GameManager(){
}
}
and Board:
public class Board {
#Inject
public Board() {
}
}
However mBoard doesn't get instantiated. I will try more and I suppose I figure out the proper solution.
You should rather use constructor injection (like for example the Thermosiphon does), and avoid field injection unless necessary. For example, let your GameManager have the Board as a constructor argument:
#Singleton
public class GameManager {
private final Board mBoard;
#Inject
public GameManager(final Board board){
mBoard = board;
}
}
Dagger will use this constructor to create an instance of the GameManager (hence the #Inject annotation), and notice it needs a Board instance. Using the ObjectGraph, it will create a Board first, and use that instance to create the GameManager. You can remove the #Provides GameManager method if you do it this way.
In your case, you have a #Provides Board method in your module. If you add an #Inject annotation to your Board constructor, you can remove this provides-method from your module:
public class Board {
#Inject
public Board() {
}
}
If you don't want to use constructor injection, the problem is that you told Dagger that you want to create your GameManager instance yourself (because you have the #Provides GameManager method). If you remove this method, and let Dagger create it for you like above but without the Board parameter in the constructor, Dagger will also notice the #Inject Board field and inject that as well.
A final remark. Remove the library = true and complete = false statements! These are not necessary at all in this example. Only add them if you really know what you're doing. By not having them, Dagger will create compile-time errors to notify you that something is wrong. If you do include them, you're telling Dagger "Hey, I know what I'm doing, don't worry, it's all correct", when in fact it isn't.
Edit
A quote from the Dagger1 site:
If your class has #Inject-annotated fields but no #Inject-annotated
constructor, Dagger will use a no-argument constructor if it exists.
Classes that lack #Inject annotations cannot be constructed by Dagger.
I do not use this method very often, so I could be wrong. I think this means that you should remove the #Inject annotation from your constructor, like so:
#Singleton
public class GameManager {
#Inject Board mBoard;
public GameManager(){ // Or remove the constructor entirely since it's empty
}
}
Since there is an #Inject annotation on the Board field, Dagger will know to use the no-argument constructor.
I was struggling with the same issue as most of the dagger examples everywhere use a Module with Provides and I had a hard time finding a complete example that just does not use Provides.
I created this one. It uses field injection (not constructor injection) and works just fine through the hierarchy without requiring any call to inject. I am using Dagger 1.2.2.
Main.java
import javax.inject.*;
import dagger.*;
import dagger.ObjectGraph;
public class Main {
public static void main(String[] args) {
ObjectGraph objectGraph = ObjectGraph.create(new CarModule());
Car car = objectGraph.get(Car.class);
car.start();
}
}
CarModule.Java
import dagger.Module;
#Module(injects = Car.class)
public class CarModule {
}
Car.Java
import javax.inject.*;
public class Car {
#Inject public Engine engine;
#Inject Car() {
System.out.println("Car constructor");
}
public void start() {
engine.start();
}
}
Engine.Java
import javax.inject.*;
public class Engine {
#Inject WaterPump waterPump;
Engine() {
System.out.println("Engine Constructor");
}
void start() {
waterPump.run();
System.out.println("starting engine.");
}
}
WaterPump.Java
import javax.inject.*;
public class WaterPump {
#Inject WaterPump() {
System.out.println("WaterPump Constructor.");
}
public void run() {
System.out.println("WaterPump running.");
}
}
The output is:
Car constructor
Engine Constructor
WaterPump Constructor.
WaterPump running.
starting engine.
Without The CarModule that declares it injects Car.Class this does not work. You get:
Exception in thread "main" java.lang.IllegalArgumentException: No
inject registered for members/Car. You must explicitly add it to the
'injects' option in one of your modules.
But notice that CarModule does not #Provides anything. It is dagger that automatically creates all the dependencies using the object graph.
Also note that you don't have to place an #Inject annotation on the default constructor if you have a #Inject field in the class. For Car class I used it on both the constructor and the field and in Engine class I used it just for the field and not for the constructor and it works fine as documented.

Activity graphs and non-found dependency

I'm starting using the dagger, like it pretty much, but now facing some difficulties. My scenario is as follows: there's an activity and a dependency for it. Dependency is injected to the activity, and requires a reference to that activity. Just like this:
public class MainActivity extends BaseActivity {
#Inject ScbeHelper scbeHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
}
public class ScbeHelper {
protected static final String TAG = "scbe_helper";
private BaseActivity activityContext;
#Inject
public ScbeHelper(BaseActivity context) {
this.activityContext = context;
}
}
I'm following dagger's example from the github for activity's graphs. So I've created a similar structure in my project. First, the BaseActivity class, from which MainActivity is inherited:
public abstract class BaseActivity extends Activity {
private ObjectGraph activityGraph;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
protoApp application = (protoApp) getApplication();
// Exception happens in next line, inside plus() method
activityGraph = application.getApplicationGraph().plus(getModules().toArray());
// Inject ourselves so subclasses will have dependencies fulfilled when this method returns.
activityGraph.inject(this);
((protoApp)getApplication()).inject(this);
}
protected List<Object> getModules() {
return Arrays.<Object>asList(new ActivityModule(this));
}
public void inject(Object object) {
activityGraph.inject(object);
}
}
And the module:
#Module(injects={MainActivity.class})
public class ActivityModule {
private final BaseActivity activity;
public ActivityModule(BaseActivity activity) {
this.activity = activity;
}
#Provides #Singleton BaseActivity provideActivity() {
return activity;
}
}
Now, the problem: No injectable members on com.example.proto.BaseActivity. Do you want to add an injectable constructor? required by public com.example.proto.ScbeHelper(com.example.proto.BaseActivity)
In other words, provider method ActivityModule.provideActivity() doesn't really provide the instance of BaseActivity for some reason, though in my understanding it's set up correctly. Does anyone see an error in my setup? Am I missing something in dagger's logic?
Thanks in advance!
I'm no Dagger expert, but you have 2 issues:
you have a cyclical dependency: you helper want to have the activity injected, your activity wants to have the helper injected. I don't think Dagger can resolve this
your activity tries to get injected twice, once with the activity-level graph, once with the application-level graph
Here's what I did to get it to work:
in ScbeHelper: remove the #Inject annotation
in BaseActivity: remove ((protoApp)getApplication()).inject(this);
in ActivityModule: remove your provideActivity method (it's not going to be used anymore), and add the following method:
#Provides #Singleton ScbeHelper provideScbeHelper() {
return new ScbeHelper(activity);
}
What this does is it provides your ScbeHelper with the context it needs, but leaves only 1 annotation-driven injection so dagger can resolve it. Hope this helps.

Roboguice 2.0 injecting application into POJO

I'm newbe in Roboguice, please help. I have an application calss MyApplication in which in onCreate method i initialize some data. Also i have a POJO with buisiness logic which i want to use in my MainActivity (See code snippets below). I need to inject MyApplication into POJO to get access to data which i initialize in application's onCreate, but this code called before onCreate and i've got a NullPointerException.
public class MyApplication extends Application {
private Properties applicationProperties;
#Override
public void onCreate() {
super.onCreate();
applicationProperties = loadApplicationProperties(APPLICATION_PROPERTIES_ASSET);
}
#SuppressWarnings("unchecked")
public String getProperty(String key) {
return applicationProperties.getProperty(key);
}
}
#Singleton
public class POJO {
#Inject
private MyApplication application;
#Inject
public void init() {
// NPE here, because application onCreate not called at this moment
serverURL = application.getProperty(Constants.SERVER_URL);
}
}
public class MainActivity extends RoboActivity {
#Inject
private POJO myPOJO;
}
EDIT: Found a way to do this in RoboGuice 2.0 based on the answer in RoboGuice custom module application context.
Inject the application context in AbstractModule constructor, then bind it in configure() for later injection:
public final class MyModule extends AbstractModule
{
private final MyApplication context;
#Inject
public MyModule(final Context context)
{
super();
this.context = (MyApplication)context;
}
#Override
protected void configure() {
bind(MyApplication.class).toInstance(context);
}
}
If the data you need doesn't require a context, just access to XML resources or res/raw, you can inject that from anywhere.
Just use Roboguice.getInjector() to obtain a copy of the Resources object.

Categories

Resources