Android Dagger 2 - how to make custom scope be a local singleton - android

According to tutorials like this one: in Dagger2 we are able to provide local singleton objects using a custom scope.
I already have a global appComponent and my goal is to create a activity scope that allows a subcomponent to have local singleton providers.
My issue is when i create a custom scope and inject my activity twice, i see that the object being provided is not the same one, even though i tagged it with the custom scope. for the appComponent which uses the #Singleton annotation it works fine though.
Lets take a look at how i did this:
Here is the custom activity scope:
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ActivityScope {
}
And here is how i define the subcomponent:
#ActivityScope
#Subcomponent(modules = {PresenterModule.class, UseCaseModule.class})
public interface ActivitySubComponent {
void inject(LoginActivity target);
void inject(LoginPresenter target);
void inject(UCGetFireBaseAccount target);
}
Notice here that i've tagged this subcomponent with #ActivityScope annotation that i custom made above.
Now lets see the appComponent which is the parent component and global:
#Singleton
#Component(modules = {AppModule.class, NetworkModule.class, RepositoryModule.class})
public interface AppComponent {
void inject(myappCloudRepo target);
void inject (FireBaseCloudRepo target);
ActivitySubComponent plus(PresenterModule presenterModule, UseCaseModule useCaseModule);
}
Notice ActivitySubComponent is defined here as a subcomponent.
Finally lets look at the presenterModule class to see what i wanted to be provided as a singleton:
#Module
public class PresenterModule {
#Provides
#ActivityScope
LoginPresenter provideLoginPresenter(Context context) {
return new LoginPresenter(context);
}
}
Now when i actually use this i set it up in my application class like this:
public class MyApplication extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = initDagger(this);
}
public AppComponent getAppComponent() {
return appComponent;
}
protected AppComponent initDagger(MyApplication application) {
return DaggerAppComponent.builder()
.appModule(new AppModule(application))
.build();
}
public ActivitySubComponent getActivityComponent() {
return appComponent.plus(new PresenterModule(),new UseCaseModule());
}
}
Then lastly in any activity i do this:
public class LoginActivity extends AppCompatActivity{
ActivitySubComponent activityComponent;
//this loginpresenter is from the activitysubcomponent
#Inject
LoginPresenter loginPresenter;
//this retrofit object is from the appcomponent
#Inject
Retrofit retrofit;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityComponent= ((MyApplication)getApplication()).getActivityComponent();
activityComponent.inject(this);
Log.d("j2emanue",loginPresenter.toString());
Log.d("j2emanue",retrofit.toString());
activityComponent= ((MyApplication)getApplication()).getActivityComponent();
activityComponent.inject(this);
Log.d("j2emanue2",loginPresenter.toString());
Log.d("j2emanue2",retrofit.toString());
}
}
When i take a look at the logs after injecting TWICE i am expecting all objects to be the same but instead the loginPresenter is another instance but the retrofit is the same so that works.
Here is the log:
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue: com.myapp.mobile.myappfashion.UI.Presenters.LoginPresenter#1c27a460
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue: retrofit2.Retrofit#13366d19
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue2: com.myapp.mobile.myappfashion.UI.Presenters.LoginPresenter#3dc041de
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue2: retrofit2.Retrofit#13366d19
Notice the loginpresenter is another object so its not making a local singleton for me that would only be destroyed when i dereference the activitysubcomponent.
what am i missing here ? The reason i want it to be a local singleton is for configuration changes. This way the presenter can be maintained during a config change.

In your application class you create and return a new subcomponent.
public ActivitySubComponent getActivityComponent() {
return appComponent.plus(new PresenterModule(),new UseCaseModule());
}
Now scopes mean that within a scope an object exists only one time. But in your case, you create 2 different components. Those 2 components will share everything in their parent component (they are subcomponents), but anything within their scope will be recreated, but unique to their scope.
Anything within your component will use the same #ActivityScope annotated objects, but if you create 2 components you will have 2 copies of everything.
If you take #Singleton as an example, it does not mean the object will be an actual Singleton. It is the name of the scope that you are supposed to have for your root component, which should only be created once and be kept during the lifetime of the application.
If you were to create 2 AppComponents that are #Singleton you could observe that same behavior—two different instances of your object.
In your example Retrofit is the same, because you use the same AppComponent both times, but LoginPresenter gets recreated along with every ActivitySubComponent that you create.
With Dagger you should try that your components follow the same lifecycle as what they scope, thus your app should hold an AppComponent, and every Activity should have their own ActivityComponent (keep the component as a member variable!). When you create a new Activity you should create a new #ActivityScope scoped component, but not more often than that.
You should remove your getActivityComponent() from the Application and keep a reference to your ActivitySubComponent, because injecting scoped dependencies with the same component will give you the same objects.
activityComponent.inject(this);
activityComponent.inject(this);
// call it as many times as you'd like.
activityComponent.inject(this);
Just don't recreate your component.

Related

Dagger 2 - dependency is not singleton when using custom scope

I have custom scope:
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface PerActivity { }
and component which uses this annotation:
#PerActivity
#Component(modules = {ActivityModule.class}, dependencies = AppComponent.class)
public interface ActivityComponent {
void inject(MainActivity mainActivity);
}
and module which provides MainPresenter dependency:
#Module
public class ActivityModule {
#PerActivity
#Provides
public MainPresenter provideMainPresenter(){
return new MainPresenter();
}
}
This is MainActivity to which MainPresenter is injected:
public class MainActivity extends AppCompatActivity implements MainView {
#PerActivity
#Inject
MainPresenter mainPresenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerActivityComponent.builder()
.appComponent(DaggerAppComponent.builder().build())
.build()
.inject(this);
}
}
My problem: presenter is recreated after a configuration change (orientation change).
My question: how to make it create only once for one activity?
Dagger creates Java code that doesn't do any magic. It's some classes that can do some stuff, but they don't read/write global state or have other side effects. If you create a component, then you create all the objects that it contains. A #PerActivity scoped object provided by one component will never be the same as the one provided from another component of the same scope.
MyComponent.create().getPresenter() == MyComponent.create().getPresenter() // always false (with same scope)
This said, you have one major problem in your code sample, namely that you have an AppComponent, but you're keep recreating it, which is most likely not what you intended or should do. This will re-create every #Singleton for every Activity.
DaggerActivityComponent.builder()
.appComponent(DaggerAppComponent.builder().build()) // DON'T !!
.build()
.inject(this);
You need to cache this component in your Application class and retrieve it, e.g. something like ((MyApp) getApplication).getComponent(). Otherwise none of your #Singleton classes get reused.
All this said, a presenter that is scoped #PerActivity will always be a new object when you create a new ActivityComponent (DaggerActivityComponent.build()) in your onCreate method. You recreate the component, and that new component will create new objects. This is the intended behavior, because otherwise you will get memory leaks and other unintended side effects. (Imagine the presenter kept a reference to the activity! Memory will leak, and UI updates will crash or be ignored.)
To reuse the same presenter you have to move the presenter up in scope. You can either make the presenter a #Singleton and keep it in your AppComponent, or you can try to find a way to add another layer of components in-between (AppComponent -> ActivityCacheComponent -> ActivityComponent), but most project that I have seen won't add this in-between layer as it adds a lot of complexity. You either have singletons that hold data and should be long-lived, or you have presenters that are cheap to create and can be easily recreated along with the Activity.

Dagger 2 how to access same component everywhere

How do I create a singleton object that I can access anywhere from any place in my code?
Objects(1) from which I want to inject my signleton component can't be created withing dagger.
Those objects don't have common dependencies which I can access (like getApplication()
Quick to an example:
InjectedClass:
public class InjectedClass {
public InjectedClass() {
System.out.println("injected class created");
}
}
HolderClassA:
public class HolderClassA { // this is one of object I marked with (1)
#Inject InjectedClass b;
public HolderClassA() {
Injector build = DaggerInjector.builder().build();
build.inject(this);
System.out.println(b);
}
}
HolderClassB:
public class HolderClassB { // this is another object I marked with (1)
#Inject InjectedClass b;
public HolderClassB() {
Injector build = DaggerInjector.builder().build();
build.inject(this);
System.out.println(b);
}
}
Provider:
#Module
public class Provider {
#Provides
#Singleton
InjectedClass provideInjectedClass() {
return new InjectedClass();
}
}
Injector:
#Component(modules = {Provider.class})
#Singleton
public interface Injector {
void inject(HolderClassA a);
void inject(HolderClassB a);
}
Somewhere in code:
new HolderClassA();
Somewhere else in code that is NO WAY related to previous code, nor has the same parent or can access same objects (like in dagger guide with getApplication()):
new HolderClassB();
Actual result: two instances of InjectedClass are crated
Expected result: a single instance of InjectedClass is created.
The issue is DaggerInjector.builder().build(); creates different scopes which don't know about each other. For example, I can solve this issue by creating static variable DaggerInjector.builder().build() and call inject from it. But thus it wouldn't be dependency injection anymore, but rather not trivial singleton pattern.
From what I understand from comments, you want separate components for your holder classes, that would inject same instance? Pardon me if I am wrong.
You can try this.
#Component(modules = arrayOf(AppModule::class))
#Singleton
interface AppComponent {
//sub components
fun plusHolderClass1(holderModule: HolderModule1):HolderComponent1
fun plusHolderClass2(holderModule: HolderModule2):HolderComponent2
//provision methods
fun getInjectedClass():InjectedClass
}
This is your application component, or the top level component, that you initialise in your application, of course using your Module class that will provide the Injected class as a singleton.
#Subcomponent(modules = arrayOf(HolderModule1::class))
#ActivityScope
interface HolderComponent1{
fun inject(holder:Holder1)
}
And a similar one for Holder2 class. You can define your local scope dependencies in the modules.
But of course even in this case you have to store the instance of appComponent in Application class.
While injecting
appComponent.plusHolderComponent1(HolderModule1()).inject(yourObject)
The InjectedClass object will be injected to yourObject by fetching it from provision methods

Injecting Singleton class using Dagger 2.x

I have two components, one for Application Context and one for Activity Context as shown below.
#Singleton
#Component(modules = {AppModule.class,})
public interface AppComponent {
#ForApplication
Context context();
//Preferences prefs(); //(Question is related to this line)
}
#PerActivity
#Component(dependencies = AppComponent.class,modules = {ActivityModule.class})
public interface ActivityComponent extends AppComponent {
void inject(ActivityA activity);
}
I have a Singleton class that I want to inject in ActivityA that is declared in ActivityComponent.
#Singleton
public class Preferences {
#Inject
public Preferences(#ForApplication Context context) {
...
}
}
public class ActivityA extends AppCompatActivity {
#Inject
Preferences preferences;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerActivityComponent.builder()
.appComponent(((MyApplication) getApplication()).getComponent())
.activityModule(new ActivityModule(this))
.build();
}
I am injecting AppComponent in my Applicaiton onCreate() and ActivityComponent in onCreate of Activity, in this case ActivityA like above
Problem(or Question): Currently, If I do not expose this singleton class from my AppComponent (The commented line in first code block) and do not have provides method in AppModule. I cannot compile. Compilation Error shows I cannot have a reference to different scope from ActivityComponent, which I kind of understand. That means, I cannot access Singleton scoped class with PerActivity scoped component.
But, do I have to have provides method for all Singleton class and expose it via AppComponent (which I am currently doing and works)? Is there a better way to do the Singleton class injection?
There is another way. Declare Activity component as Application subcomponent. This way everything declared in Application component is visible in Activity subcomponent.
Access to the Activity subcomponent can be done through Acpplication component:
GithubClientApplication.get(this)
.getAppComponent()
.provide(new ActivityModule(this))
Have a look at this excellent article about Dagger components, especially in Implementation section.

Dagger2 and Android

I am trying to implement Dagger Dependency Injection into my app but I am having a hard time understanding how it works, especially coming from Spring where DI was much easier and much more declarative.
What I want to do is have a bunch of inject-ready objects that can be used throughout my app, that is the SharedPreferences, the Network objects (OkHttp, Retrofit, Picasso...), and EventBus and a SchedulerProvider object for RxJava.
This sample seems to offer everything I need but I am having trouble grasping some concepts.
In this other sample referenced in the previous page they create a GithubService that uses the Retrofit object provided in the NetModule. For that they create a GithubComponent like this:
#UserScope
#Component(dependencies = NetComponent.class, modules = GitHubModule.class)
public interface GitHubComponent {
void inject(MainActivity activity);
}
They are using a UserScope annotation that defines its own scope. Since #Singleton cannot be used, does this mean that the object will not be a Singleton? How do scopes really affect the DI? It seems they are only declaring a named-scope with no more effect, but I'm not sure.
Also, my app is built using Activities with Fragments. Do I have to create a Component for every Fragment in my app? i.e. I need to use my REST api services all throughout the app, do I have to declare a Component for every screen using them? This raises the amount of boilerplate code required and thus sounds not very clean.
Components ought to be the "big DI provider" that provides everything for a specific scope.
For example, you could have a SingletonComponent with #Singleton scope that has every single module added to it that has at least one #Singleton scoped provider method.
#Singleton
#Component(modules={NetworkingModule.class, DatabaseModule.class, MapperModule.class, UtilsModule.class})
public interface SingletonComponent {
// provision methods
OkHttpClient okHttpClient();
RealmHolder realmHolder();
// etc.
}
You can have the provision methods defined per module.
public interface DatabaseComponent {
RealmHolder realmHolder();
}
public interface NetworkingComponent{
OkHttpClient okHttpClient();
}
In which case you'd have
#Singleton
#Component(modules={NetworkingModule.class, DatabaseModule.class, MapperModule.class, UtilsModule.class})
public interface SingletonComponent
extends NetworkingComponent, DatabaseComponent, MapperComponent, UtilsComponent {
// provision methods inherited
}
In a Module, you can specify a factory method ("provider method") that specifies how to create a particular type of dependency.
For example,
#Module
public class NetworkingModule {
#Provides
#Singleton
OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()./*...*/.build();
}
#Provides
#Singleton
Retrofit retrofit(OkHttpClient okHttpClient) {
// ...
}
}
You can imagine the #Singleton scope as the big DI container that Spring would have given you.
You can also provide instances of the class using #Inject annotated constructor. This can receive any class from the component that is able to instantiate it from provider methods within that scoped component's modules (and unscoped dependencies, of course).
#Singleton
public class MyMapper {
#Inject
public MyMapper(RealmHolder realmHolder, OkHttpClient okHttpClient) { // totally random constructor for demo
}
}
or
#Singleton
public class MyMapper {
#Inject
RealmHolder realmHolder;
#Inject
OkHttpClient okHttpClient;
#Inject
public MyMapper() {
}
}
Then this will be available in the component, you can even make provision method for it to make it inheritable in component dependencies:
#Singleton
#Component(modules={...})
public interface SingletonComponent {
MyMapper myMapper();
}
With Dagger2, you can also additionally create "subscoped components", that inherit all dependencies provided from a component of a given scope.
For example, you can inherit all #Singleton scoped components, but you can still have new scoped dependencies per that new scope, such as #ActivityScope.
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ActivityScope {
}
Then, you can create subscoped components using either subcomponents, or component dependencies.
Subcomponent:
.
#ActivityScope
#Subcomponent(modules={MainActivityModule.class})
public interface MainActivityComponent {
MainPresenter mainPresenter();
}
Then this can be created in its parent scoped component:
#Singleton
#Component(modules={...})
public interface SingletonComponent {
MainActivityComponent mainActivityComponent(MainActivityModule module);
}
Then you can use the singleton component to instantiate this:
SingletonComponent singletonComponent = DaggerSingletonComponent.create();
MainActivityComponent mainActivityComponent = singletonComponent.mainActivityComponent(new MainActivityModule(mainActivityHolder));
Component dependency:
.
#ActivityScope
#Component(dependencies={SingletonComponent.class}, modules={MainActivityModule.class})
public interface MainActivityComponent extends SingletonComponent {
MainPresenter mainPresenter();
}
For this to work, you must specify provision methods in the superscoped component.
Then you can instantiate this like so:
SingletonComponent singletonComponent = DaggerSingletonComponent.create();
MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
.singletonComponent(singletonComponent)
.mainActivityModule(new MainActivityModule(mainActivityHolder))
.build();
In Dagger2, you can therefore obtain dependencies either via:
#Inject annotated constructor parameters
#Inject annotated fields on classes with #Inject annotated constructor
from #Component provision methods
via manual field injection method defined in component (for classes you can't create using #Inject annotated constructor)
Manual field injection can happen for classes like MainActivity, that you yourself don't create.
Manual field injection injects only the specific class that you are injecting. Base-classes don't get automatically injected, they need to call .inject(this) on component.
It works like this:
#ActivityScope
#Subcomponent(modules={MainActivityModule.class})
public interface MainActivityComponent {
void inject(MainActivity mainActivity);
}
Then you can do:
public class MainActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
.singletonComponent(getSingletonComponent())
.mainActivityModule(new MainActivityModule(this))
.build(); // ensure activity `holder` instead, and retain component in retained fragment or `non-configuration instance`
mainActivityComponent.inject(this);
}
}
1)
Since #Singleton cannot be used, does this mean that the object will
not be a Singleton?
GitHubComponent component has #UserScope scope, it should be #Singleton if you want to declare Singletons in this scope.
2)
How do scopes really affect the DI? It seems they are only declaring a
named-scope with no more effect, but I'm not sure.
From javax docs
A scope annotation applies to a class containing an injectable
constructor and governs how the injector reuses instances of the type.
By default, if no scope annotation is present, the injector creates an
instance (by injecting the type's constructor), uses the instance for
one injection, and then forgets it. If a scope annotation is present,
the injector may retain the instance for possible reuse in a later
injection.
3)
Do I have to create a Component for every Fragment in my app?
You can create it once, instantiate and store in your Application object and then query it everytime you need to inject something if your fragment or activity.
Check this example

Dagger 2 Scopes explanation

First of all , I am newbie, just starting to explore dagger, I have some problems with understanding, so hope someone can help me.
I have read a lot about dagger, but still cannot figure out some parts.
I created my ApplicationComponent and it looks like this
#Singleton
#Component(modules = {
ApplicationModule.class,
ThreadingModule.class,
NetworkModule.class,
DatabaseModule.class,
ServiceModule.class,
ParseModule.class,
PreferencesSessionModule.class})
public interface ApplicationComponent {
void inject(BaseActivity baseActivity);
void inject(MainAppActivity mainAppActivity);
void inject(BaseFragment baseFragment);
}
And it works great everything injects correctly, but now I wanna to dive deeper into dagger API and use Custom Scope
I have module called PermissionModule it is used for Android M versions.
#PerActivity
#Module
public class PermissionModule {
#Provides
#PerActivity
PermissionController providePermissionController(Activity activity) {
return new PermissionManager(activity);
}
}
And I want to it to be injected into my activity and be in the memory only when activity is also in memory (actvity lifecycle)
#PerActivity
#Component(modules = {
ActivityModule.class,
PermissionModule.class
})
public interface ActivityComponent {
Activity activity();
void inject(BaseActivity baseActivity);
PermissionModule permissionModule();
}
My ActivityComponent
#PerActivity
#Component(modules = {
ActivityModule.class,
PermissionModule.class
})
public interface ActivityComponent {
Activity activity();
void inject(BaseActivity baseActivity);
PermissionModule permissionModule();
}
And my BaseActivity
public abstract class BaseActivity extends AppCompatActivity implements SpiceManagerProvider, NetworkRequestsExecutor {
// Dependencies are injected by ApplicationComponent
#Inject
protected ApplicationSettingsManager mApplicationSettingsManager;
#Inject
protected SpiceManager mSpiceManager;
#Inject
protected ScheduledThreadPoolExecutor mPoolExecutor;
!!!!!!
Should be injected by activity component
#Inject
protected PermissionController mPermissionController;
And in onCreate()
#Override
protected void onCreate(Bundle savedInstanceState) {
// Injecting dependencies
MyApplication application = MyApplication.get(this);
application.getApplicationComponent().inject(this);
DaggerActivityComponent.builder().activityModule(new ActivityModule(this)).build().inject(this);
mPermissionController.requestPermission(Manifest.permission.ACCESS_FINE_LOCATION);
mPermissionController.requestPermission(Manifest.permission.CAMERA);
super.onCreate(savedInstanceState);
}
I got the error
PermissionController cannot be provided without an #Provides- or
#Produces-annotated method.
.ui.activities.base.BaseActivity.mPermissionController
What is wrong in my code ?
Also not to create new question and it is related to this topic.
How does dagger2 parse Scope annotation, I cannot figure out this. As I understand dagger only recognizes Singleton annotation and all other annotations doesn't affect dagger decision, because all other annotations will have scope of activity ?
so the problem is that you call the ApplicationComponent's inject method first
application.getApplicationComponent().inject(this);
which tries to inject all the members, including the PermissionController. But the ApplicationComponent can not provide this, and that is what Dagger complains about.
The solution is to only call the ActivityComponent's inject() method.
Most probably you do need dependencies provided by the ApplicationComponent at some point. To archive that, you'd need to combine the two components. Dagger provides two ways for that,subcomponents and component dependencies
When using component dependencies, rou would end up with something like this in your Activity's onCreate() method:
DaggerActivityComponent
.builder()
.applicationComponent(application.getApplicationComponent())
.activityModule(new ActivityModule(this))
.build().inject(this);
when you change your components to look something similar to this:
#PerActivity
#Component(
dependencies = ApplicationComponent.class,
modules = {
ActivityModule.class,
PermissionModule.class
}
)
public interface ActivityComponent {
...
}
note that you need to provide dependencies explicitly in the ApplicationComponent when you need it in the ActivityComponent (or any injectors)
If there is any dependency from parent component ( for instance #Componenet (model=AppModel.class) public interface appComponent... ) that you want to use in child component (#ActivityScope #Component (DEPENDENCY=APPCOMPONENT.class, model= ActivityModel.class) public interface activityComponenet... ) you need to expose it in parent component. Only exposed dependencies are accessible downstream (in child components). You do it by writing method from appModel that need to provide dependency downstream in appComponenet interface. Name of the method do not need to match the name of method in appModel, only the return type count.
About your confusion on Dagger scopes , I am hereby specifying some conclusions regarding scopes
Any time a un-scoped service is being injected by the same component, a new instance of a service is created.
First time a #Singleton scoped service is being injected, it is instantiated and cached inside the injecting component, and then the same exact instance will be used upon injection into other fields of the same type by the same component.
Custom user-defined scopes are functionally equivalent to a predefined #Singleton Scope
Injection of scoped services is thread safe.
If you really want to clearly understand how internally Dagger uses singleton and custom scope , follow this article Dagger 2 Scopes : How It Works Internally

Categories

Resources