Library's Activity injection using dagger - java

I start dagger2 evaluation for dependency injection in an Android application and have some questions regarding my current architecture.
I have several modules in my project:
app1/
app2/
libA/
This setup allows me to share common pieces (incl. some Activities) between several applications.
Using dagger2 to inject dependency in an application module is ok, I build the component in my app with its modules and from my Activity I retrieve the component from the Application to inject dependencies.
app1/MyApplication.java
public MyApplication extends Application
{
private MyComponent mMyComponent;
#Override
public void onCreate()
{
// bind & inject dependencies
mMyComponent = DaggerMyComponent.builder()
.myModule(new MyModule(this))
.build();
}
MyComponent getMyComponent()
{
return mMyComponent;
}
}
app1/MyActivity.java
public MyActivity extends Activity
{
#Inject
MyDependency mDependency;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
final MyComponent myComponent = ((MyApplication) getApplication()).getMyComponent();
myComponent.inject(this);
}
}
What bothers me is the tight coupling between MyActivity and MyApplication, or MyComponent to process injection.
If I have another activity MyActivity2 defined in libA/, I'm stuck and can't inject dependencies.
I'm not sure how to proceed.
I saw a similar question here: Multi-layer / libraries architecture with dagger2: designing scopes, components, modules and an answer refers to a sample application project with this kind of setup except that the library module doesn't inject dependencies in an Activity.
I made an ugly workaround to move forward but it's really not satisfying:
I added an Injector interface:
public interface Injector
{
void inject(Object target);
}
And then MyApplication implements Injector allowing me to write something like:
libA/MyActivity2
public MyActivity2 extends Activity
{
#Inject
MyDependency mDependency;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
((Injector) getApplication()).inject(this);
}
}
It cuts the dependency to both MyApplication and MyComponent.
And then, the ugly part is the inject implementation which tests objects against all supported classes (using instanceof) and call the proper inject(MyActivity2) method.
#Component(modules={MyModule.class})
#Singleton
public abstract class MyComponent
{
public void inject(Object target)
{
// FIXME the ugly part
if (target instanceof MyApplication)
inject((MyApplication) target);
else if (target instanceof MyActivity)
inject((MyActivity) target);
else if (target instanceof MyActivity2)
inject((MyActivity2) target);
}
public abstract void inject(MyApplication target);
public abstract void inject(MyActivity target);
public abstract void inject(MyActivity2 target);
}

Related

Dagger2 - Module must be set

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
}

Creating guice injector of another module installed inside current module

Have an interface that needs many implementations to be bound to it.
Going for the following design because of many constraints (May not seem good, please ignore the design).
Is it possible to create an injector for another module installed in current module while still running the configure() method for the current module.?
public class CurrentModule extends AbstractModule{
#Override
protected void configure() {
install(new OtherModule());
final someInterface getInstance = methodToGetInstance();
bind(SomeInterface.class).to(getInstance);
}
public SomeInterface methodToGetInstance() {
Injector injector = Guice.createInjector(new OtherModule());
return new ClassImplementingSomeInterface(injector.getInstance(dependency));
}
}
Yes, what you ask is possible with provider methods. This is how you should do it:
class CurrentModule extends AbstractModule {
#Override protected void configure() {
install(new OtherModule());
// Optional, but it's good to write it if the dependency becomes missing from OtherModule.
requireBinding(DependencyFromOtherModule.class);
}
#Singleton
#Provides SomeInterface createSomeInterface(DependencyFromOtherModule dependency) {
return new ClassImplementingSomeInterface(dependency);
}
}

Dagger2 Component inject for multiple Activities

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.

Dagger 2 - injecting non Android classes

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!

Override jersey resource with guice

I'm looking for a method to override jersey resources bounded with guice in GuiceServletContextListener. My code, which I'm trying to get working:
//Define Jersey resource interface
#Path("/books/{key}")
public interface BookDocument {
public BookDAO getDao();
public void setDao(BookDAO dao);
}
//Define default implementation
public class BookImpl implements Book {
#Override
public BookDAO getDao() {
return dao;
}
#Inject
#Override
public void setDao(BookDAO dao) {
this.dao = dao;
}
}
//User wants to inject his implementation, so he define it
public class BookUserImpl implements Book {
#Override
public BookDAO getDao() {
return dao;
}
#Inject
#Override
public void setDao(BookDAO dao) {
this.dao = dao;
}
}
//Inject default implementation of resource
public class ApplicationResourcesModule extends AbstractModule
{
#Override
protected void configure()
{
bind(Book).to(BookImpl);
}
}
//But user wants to inject his implementation, so he bind it in users AbstractModule
public class ApplicationResourcesModuleUser extends AbstractModule
{
#Override
protected void configure()
{
bind(Book).to(BookUserImpl);
}
}
//Bind all resources
public class JerseyGuiceConfig extends GuiceServletContextListener
{
#Override
protected Injector getInjector()
{
//Override default binding by user bindings.
return Guice.createInjector(Modules.override(new ApplicationResourcesModule()).with(new ApplicationResourcesModuleUser()), new JerseyServletModule());
}
}
But unfortunately this doesn't work, while I can't bind jersey resources in guice like interface to implementation, only bind(BookImpl.class) work's. But such binding is impossible to overwrite. If I try to override bind(BookImpl.class) with bind(BookUserImpl.class) I get an error Conflicting URI templates. The URI template /books/{key} for root resource class. while #Path should be unique. So is there any solution for my use case?
i just wan't to warn you Modules.override does not work on Guice.createInjector(Stage.PRODUCTION,...) so you should use it carefully only for Development. You should create two context listeners and somehow (trough maven profiles lets say) setup the web.xml with proper implementation.
Better to use:
//Inject default implementation of resource
public class MainModule extends AbstractModule
{
#Override
protected void configure()
{
if(currentStage().equals(Stage.PRODUCTION) {
install(new ApplicationResourcesModuleUser());
} else {
install(new ApplicationResourcesModule());
}
}
}
//Bind all resources
public class JerseyGuiceConfigPROD extends GuiceServletContextListener
{
#Override
protected Injector getInjector()
{
//Override default binding by user bindings.
return Guice.createInjector(Stage.PRODUCTION, new MainModule(), new JerseyServletModule());
}
}
public class JerseyGuiceConfigDEV extends GuiceServletContextListener
{
#Override
protected Injector getInjector()
{
//Override default binding by user bindings.
return Guice.createInjector(Stage.DEVELOPMENT, new MainModule(), new JerseyServletModule());
}
}
You can use #ImplementedBy annotation to your interface to say the default implementation should be. So, you don't have to bind it explicitly and you, if you bind it, it will override the annotation binding.
#Path("/books/{key}")
#ImplementedBy(BookImpl.class)
public interface Book {
public BookDAO getDao();
#Inject //it is enough to put the injection here, i think
public void setDao(BookDAO dao);
}
I think this problem is not related to Book and Book implementations binding, but to a binding/registering of the servlets to the Jersey container. Could you paste whole stacktrace, the guice stacktraces are verbose and very helpful.

Categories

Resources