#ContributesAndroidInjector on a activity that extends CordovaActivity - android

According to Dagger's doc I can use ContributesAndroidInjector on a concrete Android framework type. I will not be able to extend DaggerActivity as I have a hybrid app and in my case and I am using my activity by extending CordovaActivity. What should be the approach in these scenarios?
Should I fall back to pre 2.10 way of registering the Activity to be injected by Dagger as below on an activity's onCreate().
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((App)getApplication())
.getAppComponent()
.inject(this);
}
By doing this I have to rewrite my Application class to return appComponent which again defeats the purpose of DI.
Can you please guide me in the right path providing some inputs to handle this scenario.

Related

Is Dagger component bound to the object that instantiate it?

I have been using Dagger 2 in my project. I understand that the lifetime of scoped object is the same as the lifetime of component (with the same scope). What about the lifetime of component then?
For example, I have a component:
#MyApp
#Component(modules = {
ApplicationModule.class})
public interface ApplicationComponent {
// Injection methods
void inject(MyApplication mainApplication);
}
I build component by:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
buildApplicationComponent();
mApplicationComponent.inject(this);
}
private void buildApplicationComponent() {
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
}
}
Currently, the code to build component is in Application class. But
is it so that if I build component in Fragment the ApplicationComponent would have the same lifetime as the fragment & if I execute it in Application class the component would have the same lifetime as the whole application? Or how the lifetime of component is defined?
It is plain Java. You have object no matter where. The object provides you dependencies. If you use the same instance you will have the same dependencies or basically the same "Scope/lifetime". If you create somewhere new object it means new other object\dependencies it can provide so "another Scope/lifetime". If you share the object between different objects (You create it in the Application class, but reuse it in another fragment) you are in the "same Scope/lifetime".
But in general I see only "old way" of using Dagger 2 here. This is how your classes should end up everywhere. No need to find reference to any Components and etc or try to instantiate them on your own or clear them on your own in Fragments or Actvities. You have some code for Dagger 2 in one place and then some Annotations in you classes "which do the real work of your project". No need for complicated "connection" between the two parts and I see here a lot of cases like this which are part of tutorials that date back to 2016-2017...
class Repostory #Inject constructor(
private val dependency1: Dependency1
) {}
class Activity or Fragment {
#Inject lateinit var dependency2: Dependency2
}
This is a great example for right usage of Dagger2.
https://github.com/google/iosched
Here is an article about the app:
https://medium.com/#JoseAlcerreca
Jose Alcerreca is one of the Google Lead Devs responsible for creating the guidelines to write Android apps.
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector(modules = [MainActivityModule::class])
internal abstract fun mainActivity(): MainActivity
}
Just try to compile something like this. There is the exaxt same class in the app I gave you. Check the generated code and also the docs. If you see ContributesAndroidInjector there is explained:
Generates an {#link AndroidInjector} for the return type of this method. The injector is
implemented with a {#link dagger.Subcomponent} and will be a child of the {#link dagger.Module}'s component.
If you check the AndroidInjector docs you will see that it is the one that is injectin the Activity. And there it will be the implementation:
#Subcomponent(modules = {MainActivityModule.class})
#ActivityScoped
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
So in general these annotations do the magic for you when a new activity starts. They create the Subcomponent, they use it and when the Activity Lifecycle ends the Subcomponent is also dead. Dagger clears the reference. Dagger knows how Activities work or Fragments and etc. There was the old way when you craete the component in the Activity, use it, call inject on your own. Or put some component in the AppClass and then clear it on your onw. But now there is a bigger amount of annotations.

Stuck attempting to implement the official Dagger strategy to avoid cumbersome code

In my efforts to follow the good and official advice for injecting and avoiding cumbersome code (which I had) from the authors themselves, I ran into a wall when trying to use the support library.
According to the article:
AppCompat users should continue to implement AndroidInjector.Factory<? extends Activity> and not <? extends AppCompatActivity> (or FragmentActivity).
I'm sticking to an MVP architecture where views are always Fragments and I don't want to involve my Activity in any DI business, but I wonder if it's necessary for this to work but so far I haven't been able to. If I skip the whole support thing, the app crashes at runtime because the instance of the fragment is support (in case it's not obvious). Then I went into the task of trying to try to implement HasSupportFragmentInjector instead of HasFragmentInjector with a whole bunch of changes due to compile errors my mind has forgotten for the sake of my mental health. After a while I come to a point of thinking how can a non-support Activity host a support fragment. Ah! Those tricky wildcards. But no matter how I've tried to follow the advice, I can't come up with a way without an EmptyModule that I also would need to setup in the Activity so it would be visible to the fragment by dagger and its (really, for me still, magic). Why I haven't tried it? I might as well have, but I'm tired of hopeless changes and I need help at this point.
AppModule.kt
#Singleton
#dagger.Module
class AppModule(val application: Application) {
#Provides #Singleton fun application(): Application = application
...
}
AppComponent.java
#ApplicationScope
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
...
FooFragmentModule.class,
})
public interface AppComponent {
Application app();
...
void inject(MyApp app);
}
MyApp.java
public class MyApp extends Application implements HasActivityInjector {
private AppComponent component;
public AppComponent someWayToReturnAppComponent() {
...
}
#Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
#Override
public void onCreate() {
component = DaggerAppComponent.builder()
.appModule(new AppModule(this))
// more app-scoped modules
.build();
component.inject(this);
}
#Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingActivityInjector;
}
}
MainActivity.java
public abstract class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout()); // inflate the fragment via XML here
}
#Inject DispatchingAndroidInjector<Fragment> dispatchingFragmentInjector;
#Override
public AndroidInjector<Fragment> fragmentInjector() {
return dispatchingFragmentInjector;
}
}
FooFragmentComponent.java
#Subcomponent
public interface FooFragmentComponent extends AndroidInjector<FooFragment> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<FooFragment> {}
}
FooFragmentModule.kt
#dagger.Module(subcomponents = {FooFragmentComponent.class})
public abstract class FooFragmentModule {
#Binds
#IntoMap
#FragmentKey(FooFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> bindFragmentInjectorFactory(FooFragmentComponent.Builder builder);
#ActivityScope
abstract FooFragment contributeFooFragmentInjector();
#Provides
static FooPresenter presenter() {
return new FooPresenter();
}
}
FooFragment
public class FooFragment extends Fragment implements SomeView {
#Inject FooPresenter presenter;
}
OK. At this point, and going back to
AppCompat users should continue to implement AndroidInjector.Factory<? extends Activity>
I've had no need (and willingly opposing) to use it, only for the fragment. Do I really need to setup a module and component for it or am I missing something?
EDIT
After following EpicPandaForce's advice of using AndroidSupportInjectionModule, Dagger now complains that
FragmentKey methods should bind dagger.android.AndroidInjector.Factory<? extends android.app.Fragment>, not dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>.
As #EpicPandaForce mentioned in the comments, you need to use AndroidSupportInjectionModule for support Fragments. You'll also need to use the FragmentKey in dagger.android.support, not the one in dagger.android. That should get you past the problem in your edit.
To your broader point, support Fragments do not extend base Fragments (which are deprecated anyway in API 28 and beyond). This paints them in contrast to AppCompatActivity and its superclass, the support library's FragmentActivity, which both extend the framework Activity as introduced in Android API level 1. Thus, whether you're using support Fragments or built-in Fragments, you might not have a parent AppCompatActivity, but you'll always have an Activity of some sort. This is important because Android reserves the right to instantiate your Fragment using its necessary public no-arg constructor, which means that the Fragment can only self-inject using things that it can find inside onAttach (i.e. its parent fragments, its Activity, or its Application).
dagger.android is unconcerned whether your Activity is an AppCompatActivity because it does not use the Activity other than looking for its own injector. You can see that in the AndroidSupportInjection.findHasFragmentInjector private method, which checks (in order) the hierarchy of parent fragments, then the Activity, then the Application. Consequently, even though practically speaking Support Fragments will only function properly on support Activities, dagger.android can bind its keys based on the superclass Activity because there's no reason to differentiate them and set up two separate maps (Activity vs AppCompatActivity). Even if there were a separation like that, you could bind AppCompatActivity injectors into your Activity map, and everything would get terribly confusing.
You should also take from that search order that if you do not have Activity-scoped bindings, you do not need to create an Activity-scoped component; you can have your Application implement HasSupportFragmentInjector, install your FooFragmentModule directly into AppComponent, and remove HasSupportFragmentInjector from your MainActivity. This is atypical only because most apps have some sense of Activity state or controllers that should be injectable (even just injecting the Activity instance itself, or its Context or Resources). If you only have your #ActivityScope annotation because you're trying to make this work, you can skip that step entirely and only use an Application component and several Fragment subcomponents. However, I think it is very likely that you will eventually need #ActivityScope, so creating a Component for it early-on is pretty reasonable.

Injecting application context in library module with Dagger 2

I'm building an app with some features: a ContentProvider a SyncAdapter, a Job service and related persistence logic. On top on these there are the Activities with the UI. I'm trying to put all said features in a separate library module, because in theory their logic is stand-alone and would be reusable by any application.
Now comes Dagger2. The first node (main Component) of my library's dependency graph does need to provide Context, and this Context has to be injected from the Application, because the library scope has the same lifecycle of the application. To be self contained, obviously, my library should not directly use my Application class.
These are the possibilities I thought of:
Build the library's main Component in my Application and store it in a global static class/enum as suggested here but I'm concerned that using such a static reference could be an anti-pattern.
Pack in the library an Application class which builds the library scoped Component, cast app context to this class in the library to use the component and then extend this Application class on the main app. This works, but if there's more than one library it's not viable anymore.
Use the factory pattern: put provision methods in the library component that provide the factory which in turn is given the locally available context as a parameter. (As explained here). This seems viable, although it adds extra complexity.
Last but non the least, give up trying to modularize the components, since being dependent on the application context breaks the concept of modularity.
What is the correct way to do this?
Dagger 2 for Android comes to the rescue. It provides the concept of AndroidInjector, which is a Component that can be used to inject an instance in a static way, without having to know the dependency provider. Moreover, using the Dagger- prefixed classes provided out of the box, the injected dependencies look like coming from nowhere. Awesome.
All you have to do is declare in the library a top-level Module which is installed in the Application Component. This Module will provide all the dependencies and the SubComponents needed by the library, which will automatically inherit the #AppContext Context that you seeded in the dependency graph, ready to be injected anywhere in you library, as well as every dependency you provide through the main Application Component.
Here's a short example (written in Kotlin):
#Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
LibraryModule::class //plug-in the library to the dependency graph
])
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>() {
#BindsInstance
abstract fun appContext(#AppContext context: Context)
override fun seedInstance(instance: App) {
appContext(instance)
}
}
}
Edit: extended examples
An example of the Application subclass:
// DaggerApplication provides out-of-the-box support to all the AndroidInjectors.
// See the class' code to understand the magic.
public class App extends DaggerApplication {
#Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
// We only provide its own Injector, the Application Injector,
// that is the previous AppComponent
return DaggerAppComponent.builder().create(this);
}
And in your Android library:
#Module
public abstract class LibraryModule {
#ContributesAndroidInjector
public abstract LibraryActivity contributeLibraryActivityInjector();
}
public class LibraryActivity extends DaggerAppCompatActivity {
#Inject
#AppContext
Context appContext;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceSate);
// here you automagically have your injected application context!
ExternalSingleton.getInstance(appContext)
}
}

Multiple injection across same activity lifecycle results in multiple instances

There is this injector for the MainActivity defined as below in a module that is referenced by the ApplicationComponent:
#PerActivity
#ContributesAndroidInjector(modules = MainActivityModule.class)
abstract MainActivity mainActivityInjector();
and the MainActivityModule referenced by the contributor looks like this:
#Module
public class MainActivityModule {
#Provides
#PerActivity
public MyActivityDependency myActivityDependency() {
return new MyActivityDependency();
}
}
and the MainActivity itself is:
public class MainActivity extends AppCompatActivity {
#Inject
MyActivityDependency myActivityDependency;
#Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
Log.d(myActivityDependency.hashCode());
AndroidInjection.inject(this);
Log.d(myActivityDependency.hashCode());
...
}
The #PerActivity Scope is supposed to preserve Activity’s dependency instances throughout its lifecycle.
This basically means that if I perform injection (AndroidInjection.inject(this)) multiple times, I am entitled to get the same injected instance (at least that's the goal).
In that case, why different instances of the MyDependency is injected each time the “.inject()” method is called?
The #PerActivity Scope is supposed to preserve Activity’s dependency instances throughout its lifecycle.
And it does. It creates an annotated dependency in a single component only once.
AndroidInjection is just a helper class that knows how to build the component for your Activity / Fragment. It does not store or persist it. Hence...
AndroidInjection.inject(this);
will create a new component every time it is called and then inject the dependencies. It is not supposed to be called multiple times, and why would you anyways? Just call it once in onCreate and everything will work fine.
In the case that you want to inject twice, you can inject the Activities component itself, and then use the component to inject again. Doing this, using the same component, you should get the same objects every time.
#Inject
DoubleInjectActivityComponent component;
Just inject it like you would any other dependency.

Referencing the Activity inside its module

How do I use the new AndroidInjector.inject and still be able to provide an Activity instance inside an Activity Module? The Dagger docs don`t make it clear how to archive this.
The use case is the following: I have an Activity Module which provides a Presenter to my Activity, but the Presenter needs a reference to the Activity.
I used to have something like
#Inject Presenter presenter;
public onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((CustomApplication) getApplicationContext())
.getAppComponent()
.plus(new ActivityModule(this));
}
Can someone can point me to a sample that uses AndroidInjector.inject(this) instead and allow the reference of the Activity inside the Dagger 2 module?
Check Dagger 2 Github issue 615
The instance of your Activity is automatically provided, just pass it as a parameter in your module methods.
Example:
#Provides
#ActivityScope
public providePresenter(ActivityA activity) {
return new PresenterA(activity);
}
You'll now be able to abstract simple modules. Your presenter can be constructor injected too.
This actually cutout a lot of code from all my modules.

Categories

Resources