Android Studio 3.0 Canary 8
I am trying to inject my MainActivity into my Adapter. However, my solution works ok, but I think its a code smell and not the right way to do it.
My adapter snippet looks like this the but I don't like about this is that I have to cast the Activity to MainActivity:
public class RecipeAdapter extends RecyclerView.Adapter<RecipeListViewHolder> {
private List<Recipe> recipeList = Collections.emptyList();
private Map<Integer, RecipeListViewHolderFactory> viewHolderFactories;
private MainActivity mainActivity;
public RecipeAdapter(Activity activity, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
this.recipeList = new ArrayList<>();
this.viewHolderFactories = viewHolderFactories;
this.mainActivity = (MainActivity)activity;
}
#Override
public RecipeListViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
/* Inject the viewholder */
final RecipeListViewHolder recipeListViewHolder = viewHolderFactories.get(Constants.RECIPE_LIST).createViewHolder(viewGroup);
recipeListViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
/* Using the MainActivity to call a callback listener */
mainActivity.onRecipeItemClick(getRecipe(recipeListViewHolder.getAdapterPosition()));
}
});
return recipeListViewHolder;
}
}
In my Module, I pass the Activity in the module's constructor and pass it to the Adapter.
#Module
public class RecipeListModule {
private Activity activity;
public RecipeListModule() {}
public RecipeListModule(Activity activity) {
this.activity = activity;
}
#RecipeListScope
#Provides
RecipeAdapter providesRecipeAdapter(Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
return new RecipeAdapter(activity, viewHolderFactories);
}
}
In My Application class I create the components and I am using a SubComponent for the adapter. Here I have to pass the Activity which I am not sure is a good idea.
#Override
public void onCreate() {
super.onCreate();
applicationComponent = createApplicationComponent();
recipeListComponent = createRecipeListComponent();
}
public BusbyBakingComponent createApplicationComponent() {
return DaggerBusbyBakingComponent.builder()
.networkModule(new NetworkModule())
.androidModule(new AndroidModule(BusbyBakingApplication.this))
.exoPlayerModule(new ExoPlayerModule())
.build();
}
public RecipeListComponent createRecipeListComponent(Activity activity) {
return recipeListComponent = applicationComponent.add(new RecipeListModule(activity));
}
My Fragment I inject like this:
#Inject RecipeAdapter recipeAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((BusbyBakingApplication)getActivity().getApplication())
.createRecipeListComponent(getActivity())
.inject(this);
}
Even though the above design works, I think it's a code smell as I have to cast the Activity to the MainActivity. The reason I use the Activity as I want to make this module more generic.
Just wondering if there is a better way
=============== UPDATE USING INTERFACE
Interface
public interface RecipeItemClickListener {
void onRecipeItemClick(Recipe recipe);
}
Implementation
public class RecipeItemClickListenerImp implements RecipeItemClickListener {
#Override
public void onRecipeItemClick(Recipe recipe, Context context) {
final Intent intent = Henson.with(context)
.gotoRecipeDetailActivity()
.recipe(recipe)
.build();
context.startActivity(intent);
}
}
In my module, I have the following providers
#Module
public class RecipeListModule {
#RecipeListScope
#Provides
RecipeItemClickListener providesRecipeItemClickListenerImp() {
return new RecipeItemClickListenerImp();
}
#RecipeListScope
#Provides
RecipeAdapter providesRecipeAdapter(RecipeItemClickListener recipeItemClickListener, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
return new RecipeAdapter(recipeItemClickListener, viewHolderFactories);
}
}
Then I use it through constructor injection in the RecipeAdapter
public class RecipeAdapter extends RecyclerView.Adapter<RecipeListViewHolder> {
private List<Recipe> recipeList = Collections.emptyList();
private Map<Integer, RecipeListViewHolderFactory> viewHolderFactories;
private RecipeItemClickListener recipeItemClickListener;
#Inject /* IS THIS NESSESSARY - AS IT WORKS WITH AND WITHOUT THE #Inject annotation */
public RecipeAdapter(RecipeItemClickListener recipeItemClickListener, Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
this.recipeList = new ArrayList<>();
this.viewHolderFactories = viewHolderFactories;
this.recipeItemClickListener = recipeItemClickListener;
}
#Override
public RecipeListViewHolder onCreateViewHolder(final ViewGroup viewGroup, int i) {
/* Inject the viewholder */
final RecipeListViewHolder recipeListViewHolder = viewHolderFactories.get(Constants.RECIPE_LIST).createViewHolder(viewGroup);
recipeListViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
recipeItemClickListener.onRecipeItemClick(getRecipe(recipeListViewHolder.getAdapterPosition()), viewGroup.getContext());
}
});
return recipeListViewHolder;
}
}
Just one question, is the #Inject annotation need for the constructor in the RecipeAdapter. As it works with or without the #Inject.
Do not pass Activities into Adapters - This is a really bad practice.
Inject only the fields you care about.
In your example: Pass an interface into the adapter to track the item click.
If you need a MainActivity then you should also provide it. Instead of Activity declare MainActivity for your module.
#Module
public class RecipeListModule {
private MainActivity activity;
public RecipeListModule(MainActivity activity) {
this.activity = activity;
}
}
And your Adapter should just request it (Constructor Injection for non Android Framework types!)
#RecipeListScope
class RecipeAdapter {
#Inject
RecipeAdapter(MainActivity activity,
Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
// ...
}
}
If you want your module to use Activity and not MainActivity then you will need to declare an interface as already mentioned. You adapter would then declare the interface as its dependency.
But in some module you will still have to bind that interface to your MainActivity and one module needs to know how to provide the dependency.
// in some abstract module
#Binds MyAdapterInterface(MainActivity activity) // bind the activity to the interface
Addressing the updated part of the question
Just one question, is the #Inject annotation need for the constructor in the RecipeAdapter. As it works with or without the #Inject.
It works without it because you're still not using constructor injection. You're still calling the constructor yourself in providesRecipeAdapter(). As a general rule of thumb—if you want to use Dagger properly—don't ever call new yourself. If you want to use new ask yourself if you could be using constructor injection instead.
The same module you show could be written as follows, making use of #Binds to bind an implementation to the interface, and actually using constructor injection to create the adapter (which is why we don't have to write any method for it! Less code to maintain, less errors, more readable classes)
As you see I don't need to use new myself—Dagger will create the objects for me.
public abstract class RecipeListModule {
#RecipeListScope
#Binds
RecipeItemClickListener providesRecipeClickListener(RecipeItemClickListenerImp listener);
}
Personally I would do the following trick
public class MainActivity extends AppCompatActivity {
private static final String TAG = "__ACTIVITY__";
public static MainActivity get(Context context) {
// noinspection ResourceType
return (MainActivity)context.getSystemService(TAG);
}
#Override
protected Object getSystemService(String name) {
if(TAG.equals(name)) {
return this;
}
return super.getSystemService(name);
}
}
public class RecipeAdapter extends RecyclerView.Adapter<RecipeListViewHolder> {
private List<Recipe> recipeList = Collections.emptyList();
private Map<Integer, RecipeListViewHolderFactory> viewHolderFactories;
public RecipeAdapter(Map<Integer, RecipeListViewHolderFactory> viewHolderFactories) {
this.recipeList = new ArrayList<>();
this.viewHolderFactories = viewHolderFactories;
}
#Override
public RecipeListViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
/* Inject the viewholder */
final RecipeListViewHolder recipeListViewHolder = viewHolderFactories.get(Constants.RECIPE_LIST).createViewHolder(viewGroup);
recipeListViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
MainActivity mainActivity = MainActivity.get(v.getContext());
if(recipeListViewHolder.getAdapterPosition() != -1) {
mainActivity.onRecipeItemClick(
getRecipe(recipeListViewHolder.getAdapterPosition()));
}
}
});
return recipeListViewHolder;
}
}
Related
In my application , I am trying to create Dagger 2 components for
Context (To use in different classes) Which is already done
AppUtil (requires context for network check method) Need to get this done!
I have created components and initiated from Application class.
public class MyApp extends Application{
private ContextComponent mContextComponent;
private AppUtilComponent mAppUtilComponent;
private NetworkComponent mNetworkComponent;
#Override
public void onCreate() {
super.onCreate();
mNetworkComponent = createNetworkComponent();
mContextComponent = createContextComponent();
mAppUtilComponent = createAppUtilComponent();
}
private AppUtilComponent createAppUtilComponent() {
return DaggerAppUtilComponent.builder().appUtilModule(new AppUtilModule(this)).build();
}
public AppUtilComponent getAppUtilComponent() {
return mAppUtilComponent;
}
private NetworkComponent createNetworkComponent() {
return DaggerNetworkComponent.builder().networkModule(new NetworkModule()).build();
}
public NetworkComponent getNetworkComponent() {
return mNetworkComponent;
}
private ContextComponent createContextComponent() {
return DaggerContextComponent.builder().contextModule(new ContextModule(this)).build();
}
public ContextComponent getContextComponent(){
return mContextComponent;
}
}
The ContextModule class is as follows
#Module
public class ContextModule {
private Context mContext;
public ContextModule(Context context){
mContext = context;
}
#Provides
#Singleton
Context getContext(){
return mContext;
}
}
Context component will be
#Singleton
#Component (modules = ContextModule.class)
public interface ContextComponent {
void inject(AppUtils appUtils);
}
AppUtilModule is like
#Singleton
#Component (modules = AppUtilModule.class)
public interface AppUtilComponent {
void inject(SplashActivity splashActivity);
}
With this AppUtilModule have modified as
#Module (includes = ContextModule.class)
public class AppUtilModule {
private AppUtils mAppUtils;
private Context context;
#Inject
public AppUtilModule(Context context) {
this.context = context;
}
#Provides
public AppUtils getAppUtil(){
mAppUtils = new AppUtils(context);
return mAppUtils;
}
}
Now my AppUtils is getting context injected , which is perfectly alright.
public class AppUtils {
public Context mContext;
#Inject
public AppUtils(Context _context){
mContext = _context;
}
public boolean isNetworkConnected(){
//Using mContext to determine Network
}
... Other Utility methods which will be used throughout my application
}
But now how can I make AppUtil as a separate Dagger component (which
is internally having Context as a Dependency) and inject in other classes?
EDIT 1: After making constructor injection of context into AppUtils and using AppUtil component in SplashActivity which already had Network component
After making the AppUtil as Dagger dependency, now Project is giving
compile time error. Before this changes, NetworkProcessor in SplashActivity used to work
fine, as it was only the dependency SplashActivity had. What is that I
am missing/ doing wrong!
public class SplashActivity ....
{
#Inject
public NetworkProcessor mNetworkProcessor;
.....
#Inject
public AppUtils mAppUtils;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApp.getInstance().getAppUtilComponent().inject(this);
MyApp.getInstance().getNetworkComponent().inject(this);
}
}
Error:
rror: [Dagger/MissingBinding] com.dev.myapp.networking.NetworkProcessor cannot be provided without an #Inject constructor or an #Provides-annotated method.
com.dev.myapp.networking.NetworkProcessor is injected at
com.dev.myapp.ui.activities.landing.SplashActivity.mNetworkProcessor
com.dev.myapp.ui.activities.landing.SplashActivity is injected at
com.dev.myapp.components.AppUtilComponent.inject(com.dev.myapp.ui.activities.landing.SplashActivity)
So a component can create multiple dependencies at once, which are usually grouped by lifetime scope (Application = whole app lifetime, Activity = activity lifetime).
So if your AppUtils class has the same scope as your ContextComponent, you can just use it to inject the AppUtils into classes that should use it, e.g. an activity:
public class MainActivity extens Activity {
#Inject AppUtils appUtils;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApp) getApplication()).getContextComponent().inject(this);
}
}
after extending the ContextComponent definition to
#Singleton
#Component (modules = ContextModule.class)
public interface ContextComponent {
void inject(AppUtils appUtils);
void inject(MainActivity activity);
}
and you need to change AppUtils to use conastructor injection:
public class AppUtils {
private Context mContext;
#Inject
public AppUtils(Context context){
this.mContext = context;
}
}
After Question Edit 1
Dagger does not know how to create the NetworkProcessor-class, hence the compiler error. To make it work, you should change NetworkProcessor to have a constructor that is annotated with #Inject like you did with AppUtils (the second option would be creating a #Provides method in a dagger module, but the first solution is easier).
You did not supply the source code of NetworkProcessor so I'm assuming here it only needs a Context as a dependency.
Now that both AppUtils and NetworkProcessor have an injectible constructor that only has Context as an argument, Dagger can create the missing links by itself.
You don't need the NetworkComponent and the AppUtilComponent, just one Component, so delete them. Also delete the NetworkModule and the AppUtilModule. ContextComponent now is sufficient to inject all the dependencies into SplashActivity:
public class SplashActivity .... {
#Inject public NetworkProcessor mNetworkProcessor;
.....
#Inject public AppUtils mAppUtils;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApp.getInstance().getContextComponent().inject(this);
}
//...
}
This works because Dagger can create the wiring code itself, since it knows how to instantiate both AppUtils and NetworkProcessor.
I have an activity which instantiate a class with an interface. How I can get the android context within MyClass with only the reference to the interface?
public class TestActivity extends Activity implements MyInterface {
#Override
public void onCreate(final Bundle savedInstanceState) {
final MyClass myClass = new MyClass(this);
}
#Override
public void onChange() {
}
}
public interface MyInterface {
void onChange();
}
public class MyClass {
public MyClass(MyInterface myInterface) {
// how to get context from myInterface ???
}
}
public class MyClass {
public MyClass(MyInterface myInterface) {
// Get Context
Context context = null;
if (myInterface instanceOf Context)
context = (Context)myInterface;
}
}
If your Activity (which extends Context) is implementing MyInterface and you pass that to MyClass, you just need to cast it to the appropriate type.
The context is only available in Activity classes or global Context, if you have to use the context, change your code:
public class TestActivity extends Activity implements MyInterface {
#Override
public void onCreate(final Bundle savedInstanceState) {
final MyClass myClass = new MyClass(this);
}
#Override
public void onChange() {
}
}
public interface MyInterface {
void onChange();
}
public class MyClass {
public MyClass(TestActivity activity) {
//now, you can use the context of your activity or do a cast
// to your interface
MyInterface interface = (MyInterface) activity;
}
}
I think you approached it a bit wrong, but I might miss understood your design concept what you want to achieve.
Create your Interface in the Class where you want to obtain the Context from and the use implement like you did in your activity.
YourContextClass() {
private MyInterface interface;
onCreate() {
interface.onChange(this);
}
public interface MyInterface() {
void onChange(Context context);
}
}
Then in your Activity class implement MyInterface, and inside the method, you can obtain the Context.
YourActivity implements MyInterface {
private Context context;
...
void onChange(Context context) {
this.context = context;
}
}
But this is only necessary if you really need the context from Interface... otherwise, in you Activity, I see you are extendingActivity, which gives you access to the Context with getContext();
I am reading documentation about Dagger2. The thing is I have clear how inject a class how has Context using this
But my doubt is with the following class for example:
public class SocialControler{
private ImageView twitterLogo;
private ImageView googleLogo;
public SocialControler(ImageView twitter, ImageView google){
twitterLogo = twitter;
googleLogo = google;
}
//Getters and Setters
}
So in my MainActivity I will have something like this
SocialControler mSocial = new SocialControler(mTwitterLogo, mGoogleLogo);
Should I inject this class using #Inject anotation instead doing new , so no more new on our Activity? If yes How?
I am stuck in the #Mudule . How I can provide a View inside de Module ?
#Module
public class AppModuleSocialMediaManager {
#Provides
public MainActivity provideActivity(){
return new MainActivity();
}
#Provides
public SocialMediaClickManager provideMediaManager(MainActivity mainActivity, View twitterLogo, View googleLogo) {
return new SocialMediaClickManager(mainActivity);
}
#Provides
public View provideTwitter(){
return ?
//from here how I can provide a View
}
}
I would not recommend the architecture you're using to build your app, i.e. make your controllers depend on exact views, but if you're fine with that then you can implement your module like this:
#Module
public class AppModuleSocialMediaManager {
private final MainActivity mainActivity;
public AppModuleSocialMediaManager(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
#Provides
#Named("twitter_view")
View provideTwitterView() {
return mainActivity.findViewById(..);
}
#Provides
#Named("google_view")
View provideGoogleView() {
return mainActivity.findViewById(..);
}
#Provides
SocialController provideSocialController(#Named("twitter_view") View twitterView, #Named("google_view") View googleView) {
return new SocialController(twitterView, googleView);
}
}
Then you need to declare another component. Let's say it's MainActivityComponent:
#Component(modules = AppModuleSocialMediaManager.class)
public interface MainActivityComponent {
void inject(MainActivity activity);
}
After that in your activity you can do something like this:
public class MainActivity extends Activity {
#Inject
SocialController socialController;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerMainActivityComponent.Builder()
.appModuleSocialMediaManager(new AppModuleSocialMediaManager(this))
.build()
.inject(this);
socialController.doSomething();
}
}
Here's my code, which I based on some old tutorial found on the internet. There really should be some examples on the main site of Dagger 2, I found it really difficult to understand how to implement all this.
It's really a lot of work to get such a simple app to run. I have two questions:
Do I have to call DaggerLoggerComponent in every class I want to get some components like my Logger class?
Also how can I make the scope of the Logger class a singleton? Right now every button click creates a new logger instance.
Probably I dont understand some underlying concepts, I've only used dependency injection in Spring before and all of this seems strange to me.
public class MainActivity extends AppCompatActivity {
private Button button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init(){
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();
component.getLogger().log("Hello!",MainActivity.this);
}
});
}
}
public class Logger {
private static int i = 0;
public Logger(){
i++;
}
public static int getI() {
return i;
}
public void log(String text, Context context){
Toast.makeText(context,text+" "+i,Toast.LENGTH_SHORT).show();
}
}
#Singleton
#Component(modules={LoggerModule.class})
public interface LoggerComponent {
Logger getLogger();
}
#Module
public class LoggerModule {
#Provides
#Singleton
Logger provideLogger(){
return new Logger();
}
}
The answer is
public class MainActivity extends AppCompatActivity {
#OnClick(R.id.button) //ButterKnife
public void onClickButton() {
logger.log("Hello!");
}
#Inject
Logger logger;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Injector.INSTANCE.getApplicationComponent().inject(this);
ButterKnife.bind(this);
}
#Override
protected void onDestroy() {
ButterKnife.unbind(this);
super.onDestroy();
}
}
public class Logger {
private static int i = 0;
private CustomApplication customApplication;
public Logger(CustomApplication application) {
this.customApplication = application;
i++;
}
public static int getI() {
return i;
}
public void log(String text){
Toast.makeText(customApplication, text + " " + i,Toast.LENGTH_SHORT).show();
}
}
public interface LoggerComponent {
Logger logger();
}
#Module
public class ApplicationModule {
private CustomApplication customApplication;
public ApplicationModule(CustomApplication customApplication) {
this.customApplication = customApplication;
}
#Provides
public CustomApplication customApplication() {
return customApplication;
}
}
#Module
public class LoggerModule {
#Provides
#Singleton
Logger provideLogger(){
return new Logger();
}
}
#Singleton
#Component(modules={LoggerModule.class, ApplicationModule.class})
public interface ApplicationComponent extends LoggerComponent {
CustomApplication customApplication();
void inject(MainActivity mainActivity);
}
public class CustomApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Injector.INSTANCE.initializeApplicationComponent(this);
}
}
public enum Injector {
INSTANCE;
private ApplicationComponent applicationComponent;
public ApplicationComponent getApplicationComponent() {
return applicationComponent;
}
void initializeApplicationComponent(CustomApplication customApplication) {
this.applicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(customApplication))
.build();
}
}
This is currently our Dagger2 architecture.
EDIT: This is from our actual code for Retrofit stuff from our application we're making:
public interface RecordingService {
ScheduledRecordsXML getScheduledRecords(long userId)
throws ServerErrorException;
}
public class RecordingServiceImpl
implements RecordingService {
private static final String TAG = RecordingServiceImpl.class.getSimpleName();
private RetrofitRecordingService retrofitRecordingService;
public RecordingServiceImpl(RetrofitRecordingService retrofitRecordingService) {
this.retrofitRecordingService = retrofitRecordingService;
}
#Override
public ScheduledRecordsXML getScheduledRecords(long userId)
throws ServerErrorException {
try {
return retrofitRecordingService.getScheduledPrograms(String.valueOf(userId));
} catch(RetrofitError retrofitError) {
Log.e(TAG, "Error occurred in downloading XML file.", retrofitError);
throw new ServerErrorException(retrofitError);
}
}
}
#Module
public class NetworkClientModule {
#Provides
#Singleton
public OkHttpClient okHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new HeaderInterceptor());
return okHttpClient;
}
}
#Module(includes = {NetworkClientModule.class})
public class ServiceModule {
#Provides
#Singleton
public RecordingService recordingService(OkHttpClient okHttpClient, Persister persister, AppConfig appConfig) {
return new RecordingServiceImpl(
new RestAdapter.Builder().setEndpoint(appConfig.getServerEndpoint())
.setConverter(new SimpleXMLConverter(persister))
.setClient(new OkClient(okHttpClient))
.setLogLevel(RestAdapter.LogLevel.NONE)
.build()
.create(RetrofitRecordingService.class));
}
//...
}
public interface RetrofitRecordingService {
#GET("/getScheduledPrograms")
ScheduledRecordsXML getScheduledPrograms(#Query("UserID") String userId);
}
public interface ServiceComponent {
RecordingService RecordingService();
//...
}
public interface AppDomainComponent
extends InteractorComponent, ServiceComponent, ManagerComponent, ParserComponent {
}
#Singleton
#Component(modules = {
//...
InteractorModule.class,
ManagerModule.class,
ServiceModule.class,
ParserModule.class
//...
})
public interface ApplicationComponent
extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {
void inject(DashboardActivity dashboardActivity);
//...
}
Do I have to call DaggerLoggerComponent in every class I want to get some components like my Logger class?
Yes for all classes that created by the system like Application, Activity and Service. but for you own classes, you don't need that. just annotate you constructor with #inject and dagger will provide your dependencies.
Also how can I make the scope of the Logger class a singleton? Right
now every button click creates a new logger instance.
Your setup for singleton is correct. but you have to initialize the component one time after the activity is created (onCreate) in order to let dagger to inject all fields. Also you can utilize lazy injection feature if you don't need the Logger object right away.
#Inject
Logger logger;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();
component.inject(this);
init();
}
Then you can access your object without take the reference from the component:
private void init(){
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
logger.log("Hello!",MainActivity.this);
}
});
}
In summary:
You have to initialize the component in all classes that use field injections.
UPDATE:
To do the actual injection, you have to declare inject() method into your component and dagger will automatically implement it. This method will take care of provide any object annotated with #Inject.
I am attempting to add Dagger 2 to my Android Project. I think I understand the concepts up to the point of where I build the graph. At that point I'm shooting in the dark and that is where I'm going wrong.
Everything compiles, but the injected field is null at Runtime.
I am attempting to start simply by injecting the Presenter into my MainActivity. I have written the following code and would appreciate some help figuring out where I have gone wrong.
My PresenterModule.java:
#Module
public class PresenterModule {
#Provides MainActivityPresenter providesMainActivityPresenter() {
return new DefaultMainActivityPresenter();
}
}
My Application class which also includes my Component following the Dagger2 example code:
public class App extends Application {
private PresenterComponent component;
#Singleton
#Component(modules = PresenterModule.class)
public interface PresenterComponent {
void inject(App app);
void inject(MainActivity activity);
}
#Override public void onCreate() {
Log.d("App.java", "Starting Application");
super.onCreate();
component = DaggerApp_PresenterComponent.builder()
.presenterModule(new PresenterModule())
.build();
component.inject(this);
}
public PresenterComponent component() {
return component;
}
}
And finally my MainActivity.
public class DefaultMainActivity
extends ActionBarActivity
implements MainActivity
{
#Inject MainActivityPresenter mPresenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((App)getApplication()).component().inject(this);
mPresenter.getCurrentDetailLineNumber();
setContentView(R.layout.base_layout);
getSupportActionBar();
mContainer = (Container) findViewById(R.id.container);
mPresenter.requestCurrentScreen();
}
The actual object to be injected is an implementation of an interface but is other wise a POJO object:
public class DefaultMainActivityPresenter implements MainActivityPresenter {
private static final int SCREEN_BROWSER = 0;
private static final int SCREEN_DETAIL = 1;
LineNumber mCurrentDetailLineNumber;
int mCurrentScreen;
#Inject
public DefaultMainActivityPresenter() {
}
...
}
Changing PresenterComponent to following will fix your problem:
#Singleton
#Component(modules = PresenterModule.class)
public interface PresenterComponent {
void inject(App app);
void inject(DefaultMainActivity activity);
}
This is due to covariance:
While a members-injection method for a type will accept instances of its subtypes, only Inject-annotated members of the parameter type and its supertypes will be injected; members of subtypes will not. For example, given the following types, only a and b will be injected into an instance of Child when it is passed to the members-injection method injectSelf(Self instance):
class Parent {
#Inject A a;
}
class Self extends Parent {
#Inject B b;
}
class Child extends Self {
#Inject C c;
}