Activity graphs and non-found dependency - android

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.

Related

Android, Using AppModule context at whole the app using dagger 2

According code below:
#Module
public class AppModule {
private Application application;
public AppModule(Application application) {
this.application = application;
}
#Singleton
#Provides
Context providesContext() {
return application;
}
#Singleton
#Provides
IAppDbHelper providesAppDbHelper() {
// a SQLiteOpenHelper class
return new AppDbHelper(application);
}
}
AppComponent:
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
void inject(MainActivity mainActivity);
void inject(SecondActivity secondActivity);
IAppDBHelper providesIAppDBHelper();
}
MainActivity:
public class MainActivity extends AppCompatActivity {
#Inject
IAppDBHelper helper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((MyApplication)getApplication()).getComponent()
.inject(this);
// It's OK
helper.getWritable().execSQL("XXX");
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
}
SecondActivity:
public class SecondActivity extends AppCompatActivity {
#Inject
IAppDBHelper helper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
// nullPointer on helper
helper.getWritable().execSQL("XXX");
}
}
After injecting my AppComponent inside my SecondActivity, nullPointer error fixes but my question is do I have to inject My AppComponent every time I want inject IAppDbHelper? So what does #Singleton and injecting in inside my MainActivity mean? Doesn't should they inject that IAppDbHelper for my SecondActivity?
You will need to get your component and inject each class that Android creates. This typically means all Activities, Fragments, and Views. Dagger can't create those classes, so the only way Dagger knows about the externally-created instance is when you pass it into the inject method on your Component.
During the injection, all #Inject fields on MainActivity and SecondActivity will be provided, and any instances that Dagger creates will also have their dependencies injected, and so forth all the way down the line. This means that you won't usually need your Component directly outside of your externally-created classes like Activities and Fragments, or (of course) your Application instance where you create the Component instance itself.
#Singleton does mean that the instance will remain the same between MainActivity, SecondActivity, and any future instances of MainActivity or SecondActivity that Android may create as you background the app. However, you still need to request injection for those classes as Android creates them, in order to receive that same instance that you've guaranteed with #Singleton.

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 can't inject a Presenter in my Activities

I'm refactoring an Android app to an MVP architecture, using Dagger 1.
My code is more or less the following:
public interface View {
Presenter getPresenter();
}
public abstract class Presenter {
// Dagger 1 can't have an abstract class without injectable members... :(
#Inject
Application dont_need_this;
protected View view;
public void takeView(View view) { ... }
}
The field in Presenter is not pretty, but what can we do?
Next, I have an Activity that's also a View.
public abstract class ActivityView extends Activity implements View {
#Inject
protected Presenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ObjectGraph scope;
// Looks for an "scoped" ObjectGraph hold in an retained fragment
if (/* fragment is null */) {
// This is not a rotation but the first time the activity is
// launched
MVPApplication app = (MVPApplication) getApplication();
ObjectGraph scope = app.createScope(this);
// Store in the fragment
}
scope.inject(this);
// setContentView, etc.
presenter.takeView(this);
}
}
All seems good for now...
public abstract class MVPApplication extends Application {
private ObjectGraph objectGraph;
#Override
public void onCreate() {
super.onCreate();
// Root scope
// createRootModules is defined in the subclass
objectGraph = ObjectGraph.create(createRootModules());
objectGraph.inject(this);
}
public abstract ObjectGraph createScope(View view);
}
With this, my application subclass has to:
define createRootModules() to instantiate every module in the root scope
check in createScope() the class of the concrete ActivityView, essentially using a HashMap<Class<? extends View>, Module> and do...
return objectGraph.plus(new ModuleForThisActivityViewClass());
For example, I have an CollectionActivityView. My app tries to...
return objectGraph.plus(new CollectionModule());
And I have the binding for this particular Presenter in this module:
#Module(addsTo = MyAppModule.class,
injects = {CollectionActivityView.class, CollectionPresenter.class}, complete=false)
public class CollectionModule {
#Provides
#Singleton
public Presenter providePresenter(CollectionPresenter presenter) {
return presenter;
}
}
But when doing the 'plus()` this error happens:
ComponentInfo{com.mycompany.myapp/com.mycompany.myapp.view.CollectionActivityView}:
java.lang.IllegalStateException: Unable to create binding for
com.mycompany.myapp.mvp.presenter.Presenter
...
Caused by: java.lang.IllegalStateException: Unable to create binding for
com.mycompany.myapp.mvp.presenter.Presenter
at dagger.internal.Linker.linkRequested(Linker.java:147)
at dagger.internal.Linker.linkAll(Linker.java:109)
at dagger.ObjectGraph$DaggerObjectGraph.linkEverything(ObjectGraph.java:244)
at dagger.ObjectGraph$DaggerObjectGraph.plus(ObjectGraph.java:203)
Dagger is trying to resolve the dangling dependency of the superclass Presenter in ActivityView before plusing the ObjectGraph with the new Module, who is in charge of providing the Presenter.
Is there a way to avoid this? Am I doing something terribly wrong? Do you have any suggestions for doing this properly?
Looking at it again, it was my fault.
I don't know why I did it (probably trying to avoid tagging the module as incomplete or library) but MyAppModule declared it was injecting CollectionActivityView, so the Presenter dependency had to be resolved in that module before plussing the other. Removed that from the annotation in MyAppModule and everything worked as intended.

Is it possible to mix constructor and field injection in Dagger?

I'm trying to use Dagger for my android project but I encounter a Problem: Is it possible to mix constructor and field injection?
I have a module and two classes:
#Module(injects = fooActivity.class)
public class Module {
private fooActivity activity;
public Module(fooActivity activity) {
this.activity = activity;
}
#Provides
public bar provideBar() {
return new bar(activity);
}
}
public class fooActivity extends Activity {
#Inject Baz baz;
public void onCreate(Bundle savedInstanceState) {
...
ObjectGraph graph = ObjectGraph.create(new BasicModule()).plus(new Module(this));
graph.inject(this);
}
}
public class bar {
#Inject Baz baz;
#Inject FooActivity activity;
public bar(FooActivity activity) {
this.activity = activity;
}
}
The problem is that 'baz' in the class 'Bar' is always null after the injection. It works though, if I don't use a provide method but only use field injection and handle all dependencies in 'Module'.
So from my point of view there are three possibilities:
1) Do field injection only and handle all dependencies in module classes.
2) Do constructor injection only and have all dependencies as constructor parameters.
3) Mix both but call .inject(this) on a ObjectGraph-object in the constructor to satisfy fields with #Inject annotation.
Is this correct?
EDIT:
I cannot use complete constructor injection because 'baz' is declared in another module. Editing the example.
#Module(includes = Module.class)
public class BasicModule {
#Provides
public baz provideBaz() {
return new baz();
}
}
I think what you are trying to do is possible. I think you want to add your activity graph to your main application graph in dagger.
Check out https://github.com/square/dagger/tree/master/examples/android-activity-graphs.
Also your class bar, you should probably do complete constructor injection. But if you MUST do it the way you are doing, you need to call inject and register bar in a module. Here is the way I would create Bar.
public class Bar {
private final Activity activity;
private final Baz baz;
#Inject public bar(FooActivity activity, Baz baz) {
this.activity = activity;
this.baz = baz;
}
}

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