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.
Related
I can't understand what wrong I am doing here
Splash Activity :
public class SplashActivity extends BaseActivity implements SplashView {
#Inject
SplashPresenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash_with_di);
presenter.getAppVersion();
}
}
Component :
#Component(modules = SplashModule.class)
public interface AppComponent {
SplashWithDiPresenter getSplashWithDiPresenter();
}
Splash Module :
#Module(includes = RetrofitModule.class)
public class SplashModule {
#Provides
SplashPresenter provideSplashPresenter(final SplashInteractorImpl interactor){
return new SplashPresenterImpl(interactor);
}
#Provides
SplashInteractor providesSplashInteractor(final ApiInterface apiInterface){
return new SplashWithDiInteractorImpl(apiInterface);
}
}
inside application class called this method in onCreate()
private void createComponent() {
appComponent = DaggerAppComponent.builder()
.splashModule(new SplashModule())
.build();
}
Getting null object reference on Splash activity on create method
-> presenter.getAppVersion();
You have injected your application dependencies and now you need to do the same with SplashActivity dependencies. So you need to create a new component for your activity, lets say SplashComponent, and add inject method to it like this:
#PerActivity
#Component(modules = SplashModule.class, dependencies = AppComponent.class)
public interface SplashComponent {
public void inject(SplashActivity activity);
}
And then in your SplashActivity in the onCreate method add injection like this:
public class SplashActivity extends BaseActivity implements SplashView {
#Inject
SplashPresenter presenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash_with_di);
DaggerSplashComponent.builder()
.appComponent(getAppication().getAppComponent())
.splashModule(new SplashModule())
.build()
.inject(this);
presenter.getAppVersion();
}
}
Note, that you need to call your presenter's method only after you have injected your dependencies with inject method!
Ideally you should look at using subcomponents for these use cases for injecting Android Components(Activities, Fragments, Services...). It's pretty simple and avoids coupling Injector Components with Android components enabling testing of these classes and switching of components for Espresso tests.
Add dagger-android deps to your project (com.google.dagger:dagger-android-support:2.x, com.google.dagger:dagger-android-processor:2.x)
And then make an abstract DepsModule module, for example -
#Module
public abstract class DepsModule {
#PerActivity
#ContributesAndroidInjector(modules={SplashModule.class})
SplashActivity provideSplashInjector()
}
And change you App Component to include AndroidSupportInjectionModule.class & DepsModule.class and you should remove SplashModule.classs from the list to avoid rebinding errors.
Now its as easy as having a DispatchingAndroidInjector<Activity> instance injected to you App class and implementing HasActivityInjector interface to return the dispatching injector.
Now in SplashActivity before invoking super.onCreate() call AndroidSupportInjection.inject(this)
#ContribbutesAndroidInjector automatically tells dagger to create a sub-component for SplashActivity and bind the injector factory with DispatchingAndroidInjector<Activity> removing the need for boilerplate sub-component classes
And other sub-component installations for different activities can be added to DepsModule to enable injecting them in a similar way.
This also helps in scope segregation as exposing a splash activity's bindings to the entire app component is not really good
Hope I could help. You can visit the dagger 2 android docs for more info on 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.
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.
I want to replace our component registry (with dexfile class loader magic) with an dependency injection framework for Android.
The first try is dagger.
When trying I get the following error:
11-06 13:05:41.040 16269-16269/com.daggertoolkitexample E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{daggertoolkitexample/com.dagger.MyActivity}: java.lang.IllegalArgumentException: No inject registered for members/com.dagger.MyActivity. You must explicitly add it to the 'injects' option in one of your modules.
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2295)
...
Caused by: java.lang.IllegalArgumentException: No inject registered for members/com.dagger.MyActivity. You must explicitly add it to the 'injects' option in one of your modules.
at dagger.ObjectGraph$DaggerObjectGraph.getInjectableTypeBinding(ObjectGraph.java:302)
at dagger.ObjectGraph$DaggerObjectGraph.inject(ObjectGraph.java:279)
at com.dagger.MyApplication.inject(MyApplication.java:39)
at com.dagger.MyBaseActivity.onCreate(MyBaseActivity.java:18)
at com.dagger.MyActivity.onCreate(MyActivity.java:22)
at android.app.Activity.performCreate(Activity.java:5372)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1104)
...
I can fix it if i inject my activity in the #Module. Its work without exception.
#Module(
library = true,
injects = MyActivity.class)
public class AuthManagementModul {...}`
But this is not that i want.
I don´t can and want to know all users of my component.
Has everyone an idea what's wrong?
Here is my example code:
public class MyBaseActivity extends ActionBarActivity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication) getApplication()).inject(this);
}
}
...
public class MyActivity extends MyBaseActivity {
#Inject AuthManagement authManagement;
...
}
...
public class MyApplication extends Application{
private ObjectGraph graph;
#Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(new AuthManagementModul(this));
}
public void inject(Object object) {
graph.inject(object);
}
}
...
#Module(
library = true
)
public class AuthManagementModul {
private final Application application;
public AuthManagementModul(Application application) {
this.application = application;
}
#Provides
#Singleton
AuthManagement provideAuthManagement() {
return new AuthManagementImpl(application);
}
}
In this case, you don't want to add injects= to your AuthManagementModul, but rather to an activity-specific module which includes it.
Dagger 1.x uses injects= as a signal for what graph-roots to analyze, so they must be present -but they need not be present on leaf-node library modules - just on a module the activity uses. Consider breaking up your modules on more partitioned lines like so:
#Module(
injects = {
... all your activities
},
includes = {
AuthManagementModul.class,
ApplicationModule.class
}
)
class EntryPointsModule {}
#Module(library = true, complete = false)
class AuthManagementModul {
#Provides
#Singleton
AuthManagement provideAuthManagement(Application application) {
return new AuthManagementImpl(application);
}
}
#Module(library = true)
class ApplicationModule {
private final Application application;
public ApplicationModule(Application application) {
this.application = application;
}
#Provides
#Singleton
Application application() {
return application;
}
}
Then create your graph like so:
public class MyApplication extends Application{
private ObjectGraph graph;
#Override
public void onCreate() {
super.onCreate();
// AuthManagementModul is automatically included because it has a default
// constructor and is included by EntryPointsModule
graph = ObjectGraph.create(new EntryPointsModule(), new ApplicationModule(this));
}
public void inject(Object object) {
graph.inject(object);
}
}
There are other ways to structure this - you could just have ApplicationModule include the AuthModule and declare injects, so you only have two modules, etc. I suggested this way because ApplicationModule is then a separate concern whose only role is to hoist the Application instance into the graph, AuthManagementModul is exclusively there to support the auth function, and EntryPointsModule is there to be the front of the whole graph.
If you migrate to Dagger2, this structure is also convenient in that EntryPointsModule naturally converts to a #Component.
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.