How to specify Scope for SubComponent when using #ContributesAndroidInjector - android

I'm trying to understand the new New Android Injector with Dagger 2 from from this blog post. I understood the concept of #ContributesAndroidInjector, and how it avoids repetition of code as described in the blog
UI subcomponents(MainActivityComponent and DetailActivityComponent) are just like bridge in the graph. We don’t even have to use our brain to create this class.
So, if you want your subcomponents to be in a different scope (say #PerActivity), how would we achieve this, since we are not creating the sub-component at all?

Like this:
#PerActivity
#ContributesAndroidInjector
abstract YourActivity yourActivity();
which will generate something like this:
#Subcomponent
#PerActivity
public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
}

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.

Dagger 2 scope issue, define child fragment module. result: binding is not resolved for interface

I want to use Dagger to compose a module for AFragment with child fragments (X...Z)FragModule.
Starting out from the ActivityBindingModule, I define the an example activity's dependent modules
/* ActivityBindingModule.java */
#Module
public abstract class ActivityBindingModule{
#NonNull
#ActivityScoped
#ContributesAndroidInjector(modules = {
AFragModule.class,
BFragModule.class
// ... fragment modules
})
abstract MainActivity mainActivity();
}
In AFragModule, I define its child fragment dependent modules.
/* AFragModule.java */
#Module
public abstract class AFragModule{
#NonNull
#FragmentScoped
#ContributesAndroidInjector(modules = {
XFragModule.class,
YFragModule.class
// ... child fragment modules
})
abstract AFragment providesFragment();
}
In (X...Z)FragModule, I defined its dependent objects' provider methods.
/* XFragModule.java */
#Module
public abstract class XFragModule{
#FragmentScoped
#ContributesAndroidInjector
abstract XFragment providesFragment();
#ActivityScoped
#Binds
abstract XContract.Presenter providesPresenter(XPresenter presenter);
}
I want the (X...Z)FragModule modules to be scoped within the AFragModule. And (X...Z)FragModules' presenters alive within the same scope for inter-presenter communication.
The XPresenter implementation itself uses constructor injector, with #Singleton parameters (eg. datasources)
I get the following error with the above scopes:
Cause: binding is not resolved for XContract.Presenter: ProvisionBinding{contributionType=UNIQUE, key=XContract.Presenter, bindingElement=Optional[providesPresenter(XPresenter)], contributingModule=Optional[XFragModule], kind=DELEGATE, nullableType=Optional.empty, wrappedMapKeyAnnotation=Optional.empty, provisionDependencies=[DependencyRequest{kind=INSTANCE, key=XPresenter, requestElement=Optional[presenter], isNullable=false}], injectionSites=[], unresolved=Optional.empty, scope=Optional[#ActivityScoped]}
EDIT:
I want to check my understanding of scope as well:
I "think" I understand the following
#Singleton > #ActivityScope > #FragmentScope
Scopes can't depend on the same or smaller scope
ex. #ActivityScope can't depend on #ActivityScope or #FragmentScope
Scopes can depend on any bigger scope.
ex. #FragmentScope can depend on #Singleton, #ActivityScope marked methods.
In your specific case, it looks like Dagger can't find the binding for XPresenter; you have a statement that #Binds XContract.Presenter to XPresenter, but based on your casual mention of "The XPresenter implementation itself" it looks like you might be missing a statement like:
#Binds
abstract XPresenter providesXPresenter(XPresenterImpl presenterImpl);
Scopes can depend on the same scope: Items in #ActivityScope can depend on #ActivityScope but not #FragmentScope. If you try to depend on a #FragmentScope object from #ActivityScope (a "scope-widening injection"), Dagger will prevent it and describe the components where you can find that injection.
However, you also will likely run into the trouble that a #FragmentScope component cannot contain other #FragmentScope components, which is a problem given that #ContributesAndroidInjector necessarily creates a new subcomponent that takes the scope and modules listed on the method. You'll need to adjust your choice of scope annotations, such as creating and using #ParentFragmentScope and #ChildFragmentScope. This is also important because your #ChildFragmentScope Fragment XFragment can inject objects that share the lifecycle of XFragment's subcomponent instance, AFragment's subcomponent instance, MainActivity's subcomponent instance, or your root #Singleton component.
Of course, you'll probably want to name them according to your use case, like #FullScreenFragmentScope or #TabFragmentScope or #OptionalFlowFragmentScope; you can also choose the outer scope to keep #FragmentScope and the inner scope to be #SubFragmentScope or so forth, which might be particularly useful if you have reusable modules that already use #FragmentScope. The point is precisely that a reusable module that uses #FragmentScope is not going to be clear about whether it is tracking AFragment's lifecycle or XFragment's lifecycle, so you're going to need to be clearer about that.

Dagger - Setting a Dynamic Value to SubComponent Builder [duplicate]

This question already has answers here:
Dagger 2.10 Android subcomponents and builders
(2 answers)
Closed 4 years ago.
I want to set a value to my sub-component builder at the time of building it. If simple Dagger 2 is concerned, we can achieve like following:
#UserScope
#Subcomponent(modules = {UserModule.class, ActivityBuilder.class, NewEditQuotationModule.class})
public interface UserComponent {
NewEditQuotationComponent.Builder getNewEditQuotationComponent();
void inject(UserManager userManager);
#Subcomponent.Builder
interface Builder {
#BindsInstance
Builder user(User user);
UserComponent build();
}}
But In case of Dagger Android, Subcomponent and its associated builder is handled by #ContributesAndroidInjector. Dagger Auto generate Subcomponent and its builder even its implementation with the current context.
I want to set some value at the time of building My Dagger Android Subcomponent. I tried by following approach:
#Subcomponent(modules = NewEditQuotationModule.class)
public interface NewEditQuotationComponent extends AndroidInjector<InquiriesEditNewMainActivity> {
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<InquiriesEditNewMainActivity> {
#BindsInstance
public abstract Builder setName(#Named("My_Name") String name);
}}
#Module
public abstract class NewEditQuotationModule {
#Binds
#IntoMap
#ActivityKey(InquiriesEditNewMainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindInquiriesEditNewMainActivityInjectorFactory(NewEditQuotationComponent.Builder builder);
}
I tired to build it by following way:
AndroidInjector.Builder androidInjector = MyApplication
.getApplication()
.getAppComponent()
.getApiEndPoint()
.getApiEndPointComponent()
.getUserManager()
.getUserComponent()
.getNewEditQuotationComponent().setName("My Name");
androidInjector.seedInstance(this);
androidInjector.build();
But not succeed.
Please let me know
How can I set some value at the time of building my component?
Where am I wrong in the previous approach?
dagger.android works by automatically creating your component in onCreate, and needs to work this way because Android is allowed to create your Activity on its own. Pre-creating your AndroidInjector will not work; that is what's wrong with your previous approach.
As I tried to indicate through a duplicate-question vote, the only way for you to set module instance or #BindsInstance values in dagger.android is by overriding the seedInstance method in the AndroidInjector.Builder that serves as your #Subcomponent.Builder as well. This will let you pull values out of the Activity instance and set them in your Activity graph.
Though you could always stash data in a #Singleton (ApplicationComponent-scoped) object or VM global variable, you are trying to do the right thing by passing data carefully from Activity to Activity without setting global state. I think this is the right approach, but also that your solution probably will not involve constructing an AndroidInjector explicitly. Instead, pass your data through the extras bundle, which is an idiomatic way of transferring parameters and other data into Activities; you can then insert that data into your graph by pulling it out of getIntent().getExtras() on your Activity instance in seedInstance, since getIntent() should be populated by the time onCreate(Bundle) runs.

Fragment injection without a dedicated subcomponent

I'm trying to use android-dagger to inject a fragment from a manually defined subcomponent:
#Component(modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
BuilderModule::class
])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
fun someComponent(): SomeComponent
}
#Subcomponent
interface SomeComponent {
fun inject(fragment: SomeFragment)
}
Execution fails with:
IllegalArgumentException: No injector factory bound for Class "SomeFragment"
However, if I create a fragment bind annotated with #ContributesAndroidInjector it executes fine. The doc states that all this does is create a subcomponent. Why can't I do that manually?
Minimal working project can be found on github:
https://github.com/absimas/fragment-injection
#ContributesAndroidInjector creates a subcomponent, yes. The docs don't say anything more, but they don't assert that this only creates a subcomponent; it also installs it into the Map<Class, AndroidInjector.Factory> multibinding that powers each of dagger.android's injection types.
You can see an example of this map binding on the Android page of the Dagger User's Guide:
#Binds
#IntoMap
#FragmentKey(YourFragment.class)
abstract AndroidInjector.Factory<? extends Fragment>
bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);
Note that this expects you to bind a Builder, not a Component: dagger.android expects that you'll want access to your Fragment instance from within your subcomponent, so the binding is for AndroidInjector.Factory<? extends Fragment> such that DispatchingAndroidInjector can call create(Fragment) on it. This is designed to be compatible with Subcomponent.Builder, but it does require that you define your #Subcomponent.Builder nested interface that extends the adapter AndroidInjector.Builder. Pardon my Java on a Kotlin question:
/** No need for an explicit inject, because you must extend AndroidInjector. */
#Subcomponent
interface SomeComponent extends AndroidInjector<SomeFragment> {
/**
* This abstract class handles the final create(Fragment) method,
* plus #BindsInstance.
*/
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<SomeFragment> {}
}
EDIT: It occurs to me now that your title states you don't want a dedicated subcomponent; beyond using a manual definition instead of #ContributesAndroidInjector, if you want a multi-Fragment subcomponent, then you might run into some trouble with this advice: dagger.android requires implementations of AndroidInjector<YourFragment>, and because of erasure, you own't be able to have a single class implement multiple AndroidInjector<T> or AndroidInjector.Builder<T> interfaces or abstract classes. In those cases you might need to write a manual AndroidInjector.Factory implementation which calls the correct concrete inject method. However, this seems like a lot of work for the sake of creating a monolithic Fragment component, when best-practices dictate dagger.android's default: a small and specific component for each Fragment.

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.

Categories

Resources