This seems very basic question for Dagger2 users . I have recently started exploring it with RetroFit. I have followed some tutorials and came up with the code below(some of it).
#Singleton
#Component(modules = {AppModule.class, ApiModule.class})
public interface ApiComponent {
void inject(MainActivity context);
}
public class MyApplication extends Application {
private ApiComponent mApiComponent;
#Override
public void onCreate() {
super.onCreate();
mApiComponent = DaggerApiComponent.builder()
.appModule(new AppModule(this))
.apiModule(new ApiModule("https://rect.otp/demos/"))
.build();
}
public ApiComponent getNetComponent() {
return mApiComponent;
}
}
And MainActivity.java
public class MainActivity extends AppCompatActivity {
#Inject
Retrofit retrofit;
ActivityMainBinding mainBinding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
((MyApplication) getApplication()).getNetComponent().inject(this);
ApiCall api = retrofit.create(ApiCall.class);
}
}
Questions
1. When i change void inject(MainActivity context); to void inject(Context context); i am getting a NullPointerException on retrofit in MainActivity.Why?
When use void inject(MainActivity context); its working fine. Why ?
If i need to inject RetroFit in Multiple classes what should be the approach. Creating inject() for each class is not seems the solution.
I am a newbie to dependency Injections. So Can i have some guidence on it . What will be the proper approach to use it in multiple classes.
When you declare void inject(Context context) Dagger will generate code to inject Context. Since Context does not declare any #Inject annotated fields it will end up injecting nothing. This is why your retrofit is null after the injection.
When you declare void inject(MainActivity context) it will generate code to inject MainActivity that will also set your retrofit, thus it will be initialized.
Dagger will inject parent fields, but not childrens. The class that you declare is the one that the code will be generated for.
Your default way to inject objects should be Constructor Injection where you don't have to manually declare methods or inject the objects. e.g. see this answer for reference.
Related
I have Application component with two modules - applicationModule and activityModule.
In my MvpApp.java class i create component with modules:
public class MvpApp extends Application {
#Override
public void onCreate() {
super.onCreate();
mApplicationComponent = DaggerApplicationComponent.builder()
.activityModule(new ActivityModule())
.applicationModule(new ApplicationModule(this))
.build();
}
}
My ApplicationModule has constructor with Application and i send it. But my `Activity module has constructor:
public ActivityModule(AppCompatActivity activity) {
this.mActivity = activity;
}
And how can i send my MainActivity instance into this constructor?
When i try
mApplicationComponent = DaggerApplicationComponent.builder()
.activityModule(new ActivityModule(new MainActivity()))
.applicationModule(new ApplicationModule(this))
.build();
I get an error.
First of all, it is not recommended to provide context to the dagger graph via module constructor. You should use the #BindsInstance annotation in your dagger component builder.
Also, you can not create a module with the constructor param of activity. You probably want to inject some object into your MainActivity. In this case, you need to create a method in your dagger component with a similar declaration and call it in your activity before calling
ApplicationComponent
public interface ApplicationComponent {
// your previous code
void inject(MainActivity target);
}
MainActivity
#Inject SomeClass someClass; // <- some class you want to inject from graph
#Override
protected void onCreate(Bundle savedInstanceState) {
((MvpApp) getApplicationContext()).mApplicationComponent.inject(this);
super.onCreate(savedInstanceState);
// your code
}
I have been trying to implement Dagger2.
Problem: When I use constructor injection, it works fine but when I use field injection, it throws an Error like below:
Error:(6, 48) error: cannot find symbol class DaggerApplicationComponent
/home/moderator/Downloads/Maulik/Sample Codes/Made/Dagger2Demo/app/src/main/java/com/dagger2demo/dagger2demo/di/component/ApplicationComponent.java
Error:(18, 10) error: com.dagger2demo.dagger2demo.mvp.HomePresenter cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method. This type supports members injection but cannot be implicitly provided.
com.dagger2demo.dagger2demo.mvp.HomePresenter is injected at
com.dagger2demo.dagger2demo.mvp.BaseActivity.homePresenter
com.dagger2demo.dagger2demo.mvp.BaseActivity is injected at
com.dagger2demo.dagger2demo.di.component.ApplicationComponent.inject(baseActivity)
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.
Dagger2 - My Understanding: You have to create a Module class where you will create methods. These methods will give you respective object of your needed class like Retrofit, ApplicationContext etc.. You will create an component interface in which you will define where to inject module class's dependencies.
I'm using: Retrofit, RxJava - RaxAndroid, Dagger2 & MVP.
Code is below:
build.gradle(app)
// Retrofit Dependency
compile 'com.squareup.retrofit2:retrofit:2.3.0'
// Gson Converter Factory Dependency
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
// RxJava2 Adapter Dependency for Retrofit2
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
// ButterKnife Dependencies
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
// RxJava & RxAndroid Dependencies
compile group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.1.8'
compile group: 'io.reactivex.rxjava2', name: 'rxandroid', version: '2.0.1'
// Dagger2 Dependency
compile 'com.google.dagger:dagger:2.14.1'
annotationProcessor 'com.google.dagger:dagger-compiler:2.14.1'
Dagger2DemoApplication.java
public class Dagger2DemoApplication extends Application {
private ApplicationComponent mApplicationComponent;
#Override
public void onCreate() {
super.onCreate();
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule())
.build();
}
public ApplicationComponent getmApplicationComponent() {
return mApplicationComponent;
}
}
ApplicationModule.java
#Module
public class ApplicationModule {
#Provides
#Singleton
public APIEndPoints provideAPIEndPoints() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://reqres.in/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
APIEndPoints apiEndPoints = retrofit.create(APIEndPoints.class);
return apiEndPoints;
}
}
ApplicationComponent.java
#Singleton
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
void inject(BaseActivity baseActivity);
}
BaseActivity.java
public class BaseActivity extends AppCompatActivity {
// Variables
public ProgressDialog mProgressDialog;
#Inject
HomePresenter homePresenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// For Dagger2 i.e Creating instance of all provide methods defined in ApplicationModule
((Dagger2DemoApplication) getApplication()).getmApplicationComponent().inject(this);
setupProgressBar();
}
private void setupProgressBar() {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setTitle(getString(R.string.str_progress_dialog_title));
mProgressDialog.setMessage(getString(R.string.str_progress_dialog_desc));
mProgressDialog.setCancelable(false);
}
}
BaseView.java
public interface BaseView extends View {
void handleResponse(Object obj);
void showMessage(String msg);
}
View.java
public interface View {
}
BasePresenter.java
public interface BasePresenter {
void attachView(View view);
void callAPI();
}
HomePresenter.java
public class HomePresenter implements BasePresenter {
private BaseView mBaseView;
#Inject
APIEndPoints mApiEndPoints;
/*#Inject
public HomePresenter(APIEndPoints apiEndPoints) {
this.mApiEndPoints = apiEndPoints;
}*/
#Override
public void attachView(View view) {
mBaseView = (BaseView) view;
}
#Override
public void callAPI() {
// Actually calling API here with observable object - Start
Observable<Users> usersObservable = mApiEndPoints.getUsers();
usersObservable
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSuccess, this::onError);
// Actually calling API here with observable object - End
}
private void onSuccess(Users users) {
mBaseView.handleResponse(users);
}
private void onError(Throwable throwable) {
mBaseView.showMessage(throwable.toString());
}
}
HomeActivity.java
public class HomeActivity extends BaseActivity implements BaseView {
// Widgets
#BindView(R.id.rv_users)
RecyclerView rv_users;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// For ButterKnife
ButterKnife.bind(this);
// Initializing Presenter
homePresenter.attachView(this);
}
public void getDataFromServer(View view) {
mProgressDialog.show();
homePresenter.callAPI();
}
// BaseView Methods
#Override
public void handleResponse(Object obj) {
Users users;
if (obj instanceof Users) {
users = (Users) obj;
if (users != null) {
mProgressDialog.dismiss();
rv_users.setLayoutManager(new LinearLayoutManager(HomeActivity.this));
rv_users.setAdapter(new RVAdapter(users.getData()));
}
}
}
#Override
public void showMessage(String msg) {
if (msg != null) {
mProgressDialog.dismiss();
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
}
As you can see I commented Constructor Injection in HomePresenter. I'm having Field Injection there instead. But I'm not able to build the project as I'm getting error like mentioned above.
Any help will be appreciated. Let me know if any other things related to code is required.
Thanks in advance.
EDIT:
PS: I know the answer but I just can't understand why Field Injection i.e #Inject
APIEndPoints mApiEndPoints; is not working in HomePresenter. Please someone explain me.
As you can see I commented Constructor Injection in HomePresenter. I'm having Field Injection there instead.
If you use Constructor Injection, then Dagger will create the object for you and know all about it.
If you use field injection then you have to create the object and tell Dagger about it.
I don't see why you would prefer to use field injection in this case, but with field injection you need to add a #Provides annotated method to one of your modules to give Dagger access to your presenter.
You need to use either Construcotr injection, or a #Provides annotated methods in your module, just as the error states.
You're confounding dependencies producer mechanism and dependencies consumption mechanism. An annotated field is used to consume a dependency. In your case, #Inject HomePresenter homePresenter is telling Dagger "hey, I want you to inject an HomePresenter here". To do so, Dagger either needs you to define a #Provides method or to have the object constructor annotated with #Inject.
As a rule of thumb, always use #Inject annotated constructor to provide dependencies. You should only use #Provides method provider when the objects you're providing are either:
an interface
an abstract class
an object coming from an external library (you don't have access to the constructor)
an object which requires customization before being provided
In your case, you got your error because you don't have a #Provides annotated method nor an #Inject annotated constructor. You should uncomment your constructor as it is the way to go in your situation.
I'm implimenting Dagger 2 in my Android app. I have it setup in the following way:
AppComponent.java
#Singleton
#Component(modules = {
AndroidInjectionModule.class,
AndroidSupportInjectionModule.class,
ActivityBuilder.class,
AppModule.class,
DataBaseDaoModule.class
})
public interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder application(Application aApplication);
AppComponent build();
}
Application application();
void inject(MyApplication aApplication);
}
AppInjector.java
ublic class AppInjector {
public static void init(MyApplication aApplication) {
//Initialize dagger and inject the aApplication
DaggerAppComponent.builder().application(aApplication).build().inject(aApplication);
aApplication.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
#Override
public void onActivityCreated(Activity aActivity, Bundle aBundle) {
handleActivity(aActivity);
}
#Override
public void onActivityStarted(Activity aActivity) {
}
#Override
public void onActivityResumed(Activity aActivity) {
}
#Override
public void onActivityPaused(Activity aActivity) {
}
#Override
public void onActivityStopped(Activity aActivity) {
}
#Override
public void onActivitySaveInstanceState(Activity aActivity, Bundle aBundle) {
}
#Override
public void onActivityDestroyed(Activity aActivity) {
}
});
}
private static void handleActivity(Activity aActivity) {
if (aActivity instanceof HasActivityInjector) {
AndroidInjection.inject(aActivity);
Timber.d("injected Activity");
}
if (aActivity instanceof FragmentActivity) {
((FragmentActivity) aActivity).getSupportFragmentManager()
.registerFragmentLifecycleCallbacks(
new FragmentManager.FragmentLifecycleCallbacks() {
#Override
public void onFragmentCreated(FragmentManager fm, Fragment f,
Bundle savedInstanceState) {
if (f instanceof Injectable) {
Timber.d("injected Fragment");
AndroidSupportInjection.inject(f);
}
}
}, true);
}
}
}
AppModule.java
Module(includes = ViewModelModule.class)
class AppModule {
#Singleton
#Provides
ApiService providesApiService(OkHttpClient aClient, MyInterceptor aInterceptor) {
//Build a Retrofit object here
}
#Singleton
#Provides
OkHttpClient providesOkHTTPClient(MyInterceptor aInterceptor) {
//Setup OKHTTP here
}
}
And finally in MyApplication.Java in the onCreate method I just call the AppInjector like so: AppInjector.init(this);
All of this works and anything I put in my AppComponent's moduels, I can inject into Activities, Fragments and ViewModels.
However, I have cases where I would need a utility class, that depends on Application, for contex - and I use the utility class in various places. Or I will have a Manager class, that depends on Application, or needs something from AppModule. However, since I use these classes outside of Activities, Fragments and ViewModels I cannot just inject. How would I provide my utility classes with their dependencies and any other type of class - like a manager class?
My first thought was to create a UtilityComponent and a ManagerCompoent of sorts, however I have no idea how I would get them to work with anything in AppModuel or through my AppComponent.
Please don't just use component.inject(myObject) for everything. Always prefer constructor injection or provide it from a module where you can do additional setup steps. .inject(myObject) is intended for Framework components where you don't have access to the constructor.
My first thought was to create a UtilityComponent and a ManagerCompoent of sorts, however I have no idea how I would get them to work with anything in AppModuel or through my AppComponent.
You don't need a separate component for that. See below.
However, since I use these classes outside of Activities, Fragments and ViewModels I cannot just inject.
That has nothing to do with injection. You're talking about scopes, and it sound like your utilities are a #Singleton. Your AppComponent is a #Singleton scoped component, hence it can be used to provide your utils, too.
However, I have cases where I would need a utility class, that depends on Application, for context
If they are part of the #Singleton component, which has access to your Application, they can also be provided anywhere else. No need for more components or anything. Just declare your dependencies and don't overthink it.
Just declare your util, annotate it with #Singleton and mark the constructor with #Inject for constructor injection. #Singleton ensures that it will be provided by your AppComponent and can access the Application on which it depends.
#Singleton public class MyUtil {
private Application application;
#Inject public MyUtil(Application application) {
this.application = application;
}
}
And then you can just inject it in your Activities, Fragments, or even into other Utilities....
#Singleton public class MyUtilWrapper {
private MyUtil myUtil;
#Inject public MyUtilWrapper(MyUtil myUtil) {
this.myUtil = myUtil;
}
}
And you can inject either or both into your activity or fragment...
#Inject MyUtil myUtil;
#Inject MyUtilWrapper myUtilWrapper;
void onCreate(..) {
AndroidInjection.inject(this);
}
You do not need any modules, provides methods, or components to provide simple classes. Just make sure to add the right scope!
I'm new to dagger and my constructor/method injection doesn't seem to work.
Doesn't work meaning the injected fields are still requested.
This is my code:
#Module
public class AppContextModule {
private final Context appContext;
public AppContextModule(Context appContext) {
this.appContext = appContext;
}
#Singleton
#Provides
public Context getAppContext() {
return appContext;
}
}
#Singleton
#Component(modules = {AppContextModule.class})
public interface MyComponent {
void inject(ActivitiesLifeCycleListener obj);
void inject(WebViewManager obj);
Context context();
}
public final class MyClass {
private final WeakReference<Context> mAppContext;
#Inject
public MyClass(Context context) {
this.mAppContext = context
}
}
public class MyActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyClass my = new MyClass(); // The IDE requests context!
}
}
"MyClass" still requires "Context" although I used the "#Inject" annotation above the constructor.
What am I missing?
Do I even need to expose my Context?
Thanks.
Well...you declare your constructor
public MyClass(Context context) {
this.mAppContext = context
}
And then call it without any parameters
MyClass my = new MyClass(); // The IDE requests context!
Why do you think this should work? This is plain java, no dagger involved, and you are trying to create some new object without supplying the arguments needed.
Dagger is no magic that changes how java works. To use constructor injection you have to actually use Dagger to inject your fields.
public class MyActivity extends BaseActivity {
#Inject
MyClass mMyClass; // mark field for injection
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// todo ... create component
component.inject(this); // inject fields
}
}
Given that you provide a Context somewhere (you'll get a Context must be provided... compile error if it is not) dagger will then inject your activity with MyClass by calling inject—no magic. The inject method will assign the object to your field.
Constructor injection only means that you don't have to use a module and include a method to provide the object. It does not just magically create objects out of thin air or change the way constructors work.
I never tried Guide or other DI library, but trying to use Dagger from square for Android application. It works great for Frgements, but not for POJO. The user guide assumes some knowledge on DI as it doesn't explain in greater detail. What should I do to inject restAdapater into my POJO. If I do field injection, with the same code, it works in Fragment.
public class MyApplication extends Application {
private ObjectGraph objectGraph;
#Override
public void onCreate() {
super.onCreate();
objectGraph = ObjectGraph.create(new DIModule(this));
}
public ObjectGraph objectGraph() {
return objectGraph;
}
public void inject(Object object) {
objectGraph.inject(object);
}
...
#Module(entryPoints = {
MainActivity.class,
.....,
Auth.class,
RestAdapter.class
})
static class DIModule {#Provides
#Singleton
public RestAdapter provideRestAdapter() {
return new RestAdapter.Builder().setServer(
new Server(Const.BASE_URL)).build();
}
}
}
//POJO
public class Auth {
#Inject
RestAdapter restAdapater;
String Username;
String Password;
public String authenticate() {
...
Api api = restAdapater.create(..) // **restAdapater is null**
}
}
All the fragments are derived from the below, and DI works fine in them. In a recent talk by Eric burke, he explains this is necessary because Android constructs the object.
public class BaseFragment extends Fragment {
#Override
public void onCreate(Bundle state) {
super.onCreate(state);
((MyApplication) getActivity()
.getApplication())
.inject(this);
}
}
If you create an Auth instance yourself, then Dagger wouldn't be aware of this instance and will not be able to inject the dependencies for you.
Since you have already declared Auth.class in the Module entryPoints, you just need to ask ObjectGraph for Auth instance:
Auth auth = objectGraph.get(Auth.class);
Dagger then would know what is required to provide an instance of Auth, i.e. inject it with your RestAdapter.