DI with Dagger 2, replace sub-component on built component - android

I'm relatively new to Dagger2 but I've come to love the advantages of using it on my projects. I'm currently trying to understand Custom Scopes.
I have this basic app setup: ApplicationComponent, ActivityComponent, UserComponent. And this is how I intend them to work in my app
[-----------User scope-------------]
[ Activity scope ][ Activity scope ][ Activity scope ][ Activity scope ]
[-----------------------Aplication Scope (Singleton)-------------------]
In the two activities in the middle the user is logged in.
My dependency graph looks like this: AplicationComponent <- ActivityComponent <- UserComponent
UserComponent depends in ActivityComponent to work, and ActivityComponent depends on AplicationComponent.
UserComponent is just a "Specialized" ActivityComponent that also provides the current logged in user.
Activities that dont need the user will just be injected using ActivityComponent, those who need the user injected will need to use UserComponent. Hope it makes sense.
When the user first logs in, I create an UserComponent in the current activity:
ActivtyComponent activityComponent = DaggerActivityComponent.builder()
.activityModule(new ActivityModule(this)) //** here, 'this' is the current Activity
.applicationComponent(MyApplication.getApp(getActivity()).getAppComponent())
.build();
UserComponent userComponent = DaggerUserComponent.builder()
.activityComponent(activityComponent)
.build();
userComponent.inject(this);
//Store user component to be retrieved by other activities
MyApplication.getApp(getActivity()).storeUserComponent(userComponent);
This works fine. Now, say that I start a new Activity and try to inject its dependencies. This time is a lot easier, I already have a UserComponent stored for this reason! I can just use that one, right?:
MyApplication.getApp(getActivity()).getUserComponent().inject(this);
Wrong!... It will crash! because that component still has the previous activity stored in its activity module (**see code above)
And I don't want to create another UserComponent, that would render the scope useless... all provides methods will be called again, am I right?
I need that specific component, not a new one. But I have to somehow swap its ActivityComponent for a new one, the new one will have this activity passed in in its activityModule... that's my question:
Is it possible? Am I looking at this the right way?
Can I change sub components in already built components?
Thanks in advance

Usually the way most tutorials show it is that you have your dependencies like AppComponent <- UserComponent <- ActivityComponent
Components create scoped objects once and if something changes you should create a new component. There is no hot swapping modules or objects in dagger 2 and if you try thinking this through you see why:
If you provide dependency A, then use A everywhere, then replace A with NEW-A and start using NEW-A from that point on...That is a really inconsistens state that you might wanna avoid.
A component should live in its respective life cycle. If your component keeps a reference to the activity it should be used along with just this activity or it will lead to a memory leak (or errors like yours).
If your user component depends on the application, then you can store that component within the application without creating any issues. Your activities then just create their own, scoped components—using and depending on either the application- or user component.

Related

#UserScope with dagger-android

I have been forcing myself to use dagger-android for my new project, in order to reduce all the boiler plate for subComponents for activities and fragments.
But struggling with getting my #UserScope deps to be injected in those activities.
Reading up dagger-android it seems to support this kind of object graph:
Application -> Activity -> Fragment -> Sub-fragments
In my case I need a UserScope to sit between Application and Activity. i.e
|-> Activity -> Fragment -> Sub-fragments`
Application -> User - |
|-> Activity -> Fragment -> Sub-fragments`
I was wondering if there is way to achieve this WITH using ContributesAndroidInjector along with a custom subcomponent.
Any advice.
Couple of similar threads:
Make a UserScope with Dagger2 that lives for multiple activities and fragments
https://github.com/google/dagger/issues/1267
If you want to use AndroidInjection and ContributesAndroidInjector I don't think that is possible, if not in some degenerate way.
To be more precise, when you use AndroidInjection the code is getting the Application instance from your class, and then using the HasActivityInjector the App has.
Therefore the activities' sub-components must be sub-components of the Component which injected the Application class at startup.
And when the Application class is created you can't really be in a scope different than an Application/singleton scope.
Probably the question is: what is in the User scope (and not in the application scope) and where does that come from?
If you can get that when the app starts before starting any activities, then just merge the application and the user components.
If you 'create' then user scope from some data you get from, say, an activity, then you'll have the activities pass some data using intents when they launch each other.

How to work with custom scopes and AndroidInjection.inject() in dagger 2

I want to create dagger 2 custom scope #User to manage user login state. It should lives less than Application and more than Activity. Before library have introduced AndroidInjection.inject() it not quite complex thing. You managed you components and create new component for this scope, when user logged in(from Application scoped component) and destroy it when user logged out. You can have something like this in activity onCreate()
LoginManager.getInstance()
.getUserComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
But now all code generated and we have only Application, Activity and Fragment scopes. And I cant understand how to manage my custom scope with AndroidInjection.inject() where save this component and how put it in the middle of application and activity scopes.
Thanks for any useful tutorials, documentations or examples.

Dagger2 sub-component confusion in Android

My Goal :
To understand how scope works and how to Implement a UserScope that I can use over multiple Activities and reset/create a new one as required.
Methods I am using :
This Blog: http://frogermcs.github.io/building-userscope-with-dagger2/
It apparently explains the same thing that i am trying to achieve here.
Official Docs
http://frogermcs.github.io/building-userscope-with-dagger2/
Quick brief on Blog
Obviously, There is UserModule and UserComponent. Author has wrapped the creation of UserComponent under UserManager which has ApplicationScope. So UserManager is available at time of log in. when login is successful UserComponent is initialized via UserManager. Simple logic.
Now this already initialized #UserScope is used in couple of Activities, as you can see in the picture.
What I am struggling to understand
Take a look at UserComponent.
public interface UserComponent {
#Subcomponent.Builder
interface Builder {
Builder sessionModule(UserModule userModule);
UserComponent build();
}
UserDetailsActivityComponent plus(UserDetailsActivityComponent.UserDetailsActivityModule module);
RepositoriesListActivityComponent plus(RepositoriesListActivityComponent.RepositoriesListActivityModule module);
LogoutManager logoutManager();
}
Specifically UserDetailsActivityComponent and RepositoriesListActivityComponent are created through UserComponent. Like this,
#Override
protected void onUserComponentSetup(UserComponent userComponent) {
userComponent.plus(new UserDetailsActivityComponent.UserDetailsActivityModule(this)).inject(this);
}
So they first get pre-created in UserComponent through UserManager and then it calls onUserComponentSetup which then creates the appropriate Component and injects the current Activity.
I fail to comprehend with this pattern mentioned above, as I have read in the docs that we use plus(InjectionToBeDoneOn i) when we need the injection on a particular instance of InjectionToBeDoneOn. But why inject this Activity via this Component? What does this accomplish? Wouldn't it make sense to do this the conventional way in onCreate() of the activity with DaggerXYZComponent().Builder().Build().inject(activity)?
Also, I am missing decent material of how UserScope is implemented in Android which has life span from log-in to log-out but not bigger than the #Singleton scope.
we use plus(InjectionToBeDoneOn i) when we need the injection on particular instance of InjectionToBeDoneOn
Not quite. A component has basically 3 kinds of methods
SomeDependency provideDependency() which just creates / provides some dependency to subcomponents, or for manual retrieval (basically a getter)
void inject(MyAndroidFrameworkClass object) that injects an object with its dependencies
SomeSubComponent plus(SubComponentModule module) that creates a subcomponent, adding additional modules
You're mixing up 2. and 3. here.
// user scoped component
userComponent
// create a subcomponent (UserDetailsActivityComponent)
.plus(new UserDetailsActivityComponent.UserDetailsActivityModule(this))
// use the UserDetailsActivityComponent that was just created and inject with it
.inject(this);
UserDetailsActivityComponent is a subcomponent of UserComponent, which is why the userComponent gets extended .plus(somemodule) to create a subcomponent. If your submcomponent does not need additional modules you can also just use .plus() because to Dagger the important thing is the return type or signature in general.
If it returns another component, then it creates a SubComponent.
If it hast one parameter and returns void or the parameters type, then it is an inject method
If it has no parameters and returns some type is is a provides method (1.) to expose some dependency
but why inject this Activity via this Component? What does this accomplish?
If you were to create UserDetailsActivityComponent from scratch, it would only see and know about what it can provide itself. If you have some #Singleton somewhere it could not access any of it, because it is not part of the object graph.
A subcomponent extends another component, adding to the object graph. If you have a #Singleton A and your UserComponentn needs A to provide B, with a subcomponent this will work, without it you will get a cannot be provided error.
Dagger is no magic. It really just builds up a directed graph and checks whether everything is fine. It will complain if some dependencies have cyclic dependencies on one another or if some part of the graph doesn't have access to dependencies it need.
Your UserComponent holds your userdata. For simplicity lets say it holds the UserName. Now UserDetailsActivity might want to display UserName, but it needs some way to get it.
By using the #Singleton AppComponent as a parent you'd have access to some Apis, but not the user scoped ones. You could move the user scoped objects into the #Singleton AppComponent, but then you'd either have to recreate the AppComponent every time the user changes (which kind of defeats the purpose of declaring it #Singleton, or you'd have to find some other means to update / change the user.
If you want to do it the Dagger way, you create a UserComponent that adds the User to the graph. That way subcomponents can access it and do their user things.
When the user changes you have to make sure to destroy any activities / fragments that used the UserComponent, and you just recreate everything—with a new user.
wont it make sense to do in conventional way in OnCreate() of the activity with DaggerXYZComponent().Builder().Build().inject(activity)
You can do that of course. The author just put the calls to app.getAppcomponent().getUserManager().getUserComponent() or something like this into their BaseActivity and BaseUserActivity so that you wouldn't have to repeat the same lines of code every time. This method will basically still be called in onCreate, it just enables you to use the components directly, without fetching them every single time.
You can obviously remove those template methods and inline everything in onCreate, leading to duplicated code, making maintenance harder in the long run.
i am missing decent material of how UserScope is implemented in android which has life span from log-in to log-out but not bigger than #SingleTon scope.
Android doesn't help and it's your job to clean up after yourself. If the user changes you purge everything the UserComponent and its SubComponents touched, and recreate it with the new user.
You will have to store the UserComponent with the current user either in the Application class, some "real" singleton, or some "Manager" in the application component with a #Singleton scope. Every approach has their own benefits I guess.
Scopes just point out to Dagger that one object should exist within a Scope only once. It doesn't matter what you name it, or how many objects are in the Scope. Dagger only needs them to ensure that there are no dependency cycles.

Should I use one Component for each Activity in Dagger 2?

I have the following dependencies in my Android application. What is the best approach to do this in Dagger 2?
Activity A ---- Adapter A and Adapter B and SharedPreferences
Activity B ---- Adapter B and SharedPreferences
Activity C ---- Adapter C and SharedPreferences
Will I have to make a distinct Component for each Activity? Will there have to be three separate Components?
There is some confusion about the role of Components and Modules in Dagger 2 in an Android app.
Components are for grouping similar lifecycles together.
Modules can be organized along functional lines and for testing (as per the official instructions for testing).
Some of the best examples of this are in the Google Android Architecture Blueprints Github repo. If you examine the source code there, you can see there is one single app-scoped Component (with a lifecycle of the duration of the whole app) and then separate Activity-scoped Components for the Activity and Fragment corresponding to a given functionality in a project. The Activity-scoped Components will, of course, have a lifecycle that corresponds to their respective Activity.
Let's move to your own particular use case. While one activity-scoped component that can inject all the dependencies for Activity A, B, and C may be acceptable for the simple example in the question, the situation will quickly become more complicated if requirements change and Activity A suddenly needs a new dependency with a complicated object graph. You'll then have to create a new Module for the new dependency which will only be useful for one of the three injection sites in your Component. This will, in turn, make things more difficult for testing if you are using a mock Component to test Activity B and Activity C.
Hence, I would argue that from the start it is better to maintain one Component per Activity. Activity-scoped components are cheap and easy to maintain so it is not an issue to err on the side of caution and start with one activity-scoped component per activity.
For the example you have specified, I would create an app-scoped component:
#Component( modules = { SharedPreferencesModule.class } )
#PerApp
interface AppComponent {
SharedPreferences sharedPreferences(Application app);
}
You can have your Activity components become dependent components of the app component. That way they will not have to be concerned with SharedPreferences since this is bound in the app-component and exposed to dependents:
#Component( dependencies = { AppComponent.class }, modules = { AdapterAModule.class } )
#PerActivityA
interface ActivityAComponent {
}
Note that this is the style encouraged by the dagger.android package using #ContributesAndroidInjector:
#PerActivityA
#ContributesAndroidInjector(modules = {ActivityAModule.class})
abstract YourActivity contributeYourActivityInjector();

Order of dependency injection when using scopes

I'm currently trying to figure out Dagger 2. I am trying to set up 4 scopes: App, User, Activity, Fragment. User and Activity components are Subcomponents of App. Fragment is a Component with Activity as its dependency.
Say my UserSettingsActivity needs a Toolbar (provided by ActivityModule), and a UserProfile (provided by UserModule). I won't get a UserProfile until I ask for it from the database, whereas the Toolbar can be provided right away. So the order of injection that takes place is into ActivityComponent first, then into UserComponent. I have 2 #Inject fields, one for Toolbar and one for UserProfile in the activity. I was hoping that dagger will know that the dependencies are coming from different modules, but it seems to complain that UserProfile can't be provided when injected into ActivityComponent. Obviously it can't be provided by ActivityModule, but why is it not making a connection that UserProfile is provided by UserModule?
To my best knowledge, Dagger-2 doesn't support "partial injections".
Therefore, when you call myComponent.inject(this), Dagger-2 throws an error if myComponent can't provide all #Inject annotated members of this.
I see two ways to work around this limitation:
Remove #Inject annotation from UserProfile, expose UserProfile via public method in UserComponent and inject it manually when UserComponent is ready to be used. Something analogous to this: userProfile = userComponent.getUserProfile()
Don't make UserComponent dependent on data fetching. UserComponent could be used to inject Toolbar and some UserProfileProvider at the same time, and you will fetch UserProfile from UserProfileProvider when it is available.
I personally think that second approach is the better choice. DI libraries should be used in order to satisfy objects' dependencies at construction time. In Android we can't construct Activity or Fragment ourselves, therefore we perform DI in onCreate(), onAttach(), onCreateView(), etc., but it does not mean that we should be using DI libraries in order to assist in controlling the flow of applications.
Subcomponents work's similar to inheritance(extends), in your case User component and Activity component extending App component but there is no relation between User component and Activity component so when you request User dependency in Activity it will fail.
Subcomponent can't provide any dependency to other Subcomponent.
Instead, you can make Activity component as a subcomponent of User component. This will also give you the flexibility to switch user.

Categories

Resources