How do I use Singleton in Dagger 1.x? - java

I read Christian Gruber's post and I started wondering how to use Application-wide Singletons.
In my application I have a class DBHelper, which main purpose is holding the keys to my database. I have also many (at least two) different DAOs. Now - I don't see a reason why would my several Activities/classes need more than just a single instance of DAO. What's more, why would a DAO need an instance of DBHelper just for itself? I'm pretty sure they can share, especially I don't predict a situation when both DAOs would want to perform some operation on my database at the same time. So let's see some classes:
DBHelper
#Singleton
public class DBHelper extends SQLiteOpenHelper {
//some not relevant configuration stuff
private Context context;
#Inject
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
this.context = context;
}
#Override
public void onCreate(SQLiteDatabase db) {
//db.execSQL, creating tables and stuff
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//dropping tables, creating a new one
onCreate(db);
}
}
example of DAO
public interface SomeDataDAO {
void hai();
}
public class SomeDataDAOImpl implements SomeDataDAO {
private DBHelper dbHelper;
public SomeDataDAOImpl(DBHelper dbHelper){
this.dbHelper = dbHelper;
}
#Override
public void hai() {
SQLiteDatabase database = dbHelper.getWritableDatabase();
dbHelper.doSomeDatabaseStuff()
}
}
SomeDataModule
#Module(
injects = { MainActivity.class, SomeServiceImpl.class }
)
public class SomeDataModule {
private Context appContext;
#Provides #Singleton
public SomeDataDAO provideSomeDataDao(DBHelper dbHelper){
return new SomeDataDAOImpl(dbHelper);
}
#Provides #Singleton
public ISomeService provideSomeService(SomeServiceImpl impl){
return impl;
}
#Provides
public Context getAppContext(){
return this.appContext;
}
public SomeDataModule(){
this.appContext = MainActivity.getAppContext();
}
}
Now let's see two examples of dependency consumers
public class MainActivity extends ActionBarActivity {
#Inject
SomeDataDAO someDataDAO;
private ObjectGraph objectGraph;
#Inject
ISomeService someService;
private static Context appContext;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appContext = getApplicationContext();
objectGraph = ObjectGraph.create(SomeDataModule.class);
objectGraph.inject(this);
someService.doSomeStuff();
}
public static Context getAppContext() {
return appContext;
}
}
public class SomeServiceImpl implements ISomeService {
private ObjectGraph objectGraph;
#Inject public SomeDataDAO someDataDAO;
public SomeServiceImpl(){
objectGraph = ObjectGraph.create(GraphDataModule.class);
objectGraph.inject(this);
}
public void doSomeStuff(){
someDataDAO.hai();
}
}
It works, however when I inject(this) twice, Dagger obviously creates me two instances of DBHelper as it thinks "One Singleton fon one graph instance". How do I wrap my SomeDataModule in ApplicationModule, so I have only one instance of DBHelper in the entire app? Adding #Singleton to DBHelper unfortunately was not enough.

The easy, short and the answer which solves a minor design flaw and with that your problem is the following. Instead of creating a new ObjectGraph to inject the SomeDataDAO instance in your SomeServiceImpl, you really should pass the SomeDataDAO to your constructor. That is actually what dependency injection typically means: use the constructor of some object to inject its dependencies:
public class SomeServiceImpl implements ISomeService {
private final SomeDataDAO someDataDAO;
#Inject
public SomeServiceImpl(SomeDataDAO someDataDAO){
this.someDataDAO = someDataDAO;
}
public void doSomeStuff(){
someDataDAO.hai();
}
}
Now this is where the magic happens. Notice the #Inject annotation for the constructor? It tells Dagger to use this constructor whenever an instance of SomeServiceImpl is requested, and queries the ObjectGraph for its parameters.
So when you've got this Provides method below, Dagger already knows that SomeServiceImpl depends on SomeDataDAO, and uses your provideSomeDataDAO method to provide that instance. And, hey, it's a singleton, so this is the exact same instance as any other instance of SomeDataDAO retrieved by this ObjectGraph!
#Provides #Singleton
public ISomeService provideSomeService(SomeServiceImpl impl){
return impl;
}
In fact, you don't want to use ObjectGraph.inject(...) too often, you actually want to use the above methods. There are cases however, where you don't decide what the constructor looks like. In Android for example, these are Activity and View classes, amongst others. For these special cases, ObjectGraph.inject was created, so you can still inject your dependencies using Dagger.
Final note: In SomeServiceImpl I have made the someDataDAO private. As you probably know, this is the preferred way to handle instance fields, and using injectable constructors enables this.
Turns out this answer wasn't that short at all :O)

Related

Android use dependency injection for simple custom class

after search on web for learning about this feature most topics or post was using dependency injection for Retrofit or other android useful libraries, but i have some custom class which i want to use that with DI and i can't done it, for example i have simple custom class for using SharePreference and i'm using with that as an Singleton class
in my code i can't assign correct Dagger to component on SpApplication class to use that on activities or fragments
public class SP {
private SharedPreferences preferences;
private Context context;
public SP(Context context) {
this.context = context;
}
private SharedPreferences getPrefs() {
return preferences = PreferenceManager.getDefaultSharedPreferences(context);
}
public String getString(SharedPrefsTypes propertyName) {
return getPrefs().getString(propertyName.toString(), "");
}
public int getInt(SharedPrefsTypes propertyName) {
return getPrefs().getInt(propertyName.toString(), 0);
}
...
public enum SharedPrefsTypes {
Login
}
}
now i'm trying to use DI for that:
AppModules class:
#Module
public class AppModules {
private Context context;
public AppModules(Context context) {
this.context = context;
}
#Provides
#Singleton
SP provideSharePreferences() {
SP sharePreference = new SP(context);
return sharePreference;
}
}
ApplicationComponent class:
#Component(modules = AppModules.class)
public interface ApplicationComponent {
void inject(ActivityMain activity);
}
SpApplication class:
public class SpApplication extends Application {
private static SpApplication self;
private ApplicationComponent component;
#Override
public void onCreate() {
super.onCreate();
self = this;
component = DaggerApplicationComponent.builder().build();
}
public static SpApplication get(Context context) {
return (SpApplication) context.getApplicationContext();
}
public static SpApplication getInstance() {
return self;
}
public ApplicationComponent getComponent() {
return component;
}
}
and my ActivityMain class:
public class ActivityMain extends AppCompatActivity {
#Inject
SP sharePreference;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((SpApplication) getApplication()).getComponent().inject(this);
sharePreference.setInt(SP.SharedPrefsTypes.Login, 0);
}
}
I get this error:
android.app.Application cannot be cast to com.pishguy.yendir.SpApplication
Thanks in advance
I guess you are trying to inject into ActivityMain, but since you did not provide its source, let me show you how you could inject into SpApplication. Then just copy the relevant parts into your Activity.
Few things I think you need to change.
Module:
Your AppModules class is generally OK, but I just suggest you to change the way you make use of Context - don't use it as a field, but inject it as any other service. It looks like this:
#Module
public class AppModules {
private Context context;
public AppModules(Context context) {
this.context = context;
}
#Provides // this can be non-scoped because anyway the same instance is always returned
Context provideContext() {
return this.context;
}
#Provides
#Singleton
SP provideSharePreferences(Context context) {
return new SP(context); // use method-local Context
}
}
Component:
If component injects scoped services (#Singleton is a scope), the component itself must be scoped
If you want to inject into SpApplication class, then declare it as an injection client in the component
Taking into account these two points, ApplicationComponent should look like this:
#Singleton // injects #Singleton scoped services
#Component(modules = AppModules.class)
public interface ApplicationComponent {
void inject(SpApplication application); // SpApplication is DI client (injection target)
}
Client:
I'd change few things in your SpApplication class:
The way you instantiate ApplicationComponent is incorrect
This is not a bug, but you don't really need get(Context) and getInstance() methods
In addition, since I'm showing how you inject into SpApplication, I will add the injection logic as well (which you should copy to your actual clients).
So, SpApplication (which is DI client) should look similar to this:
public class SpApplication extends Application {
#Inject SP sp; // the dependency that should be injected
private ApplicationComponent component;
#Override
public void onCreate() {
super.onCreate();
getComponent().inject(this); // this is when the actual injection takes place
}
public ApplicationComponent getComponent() {
if (component == null) {
// this is the way Dagger components should be instantiated
component = DaggerApplicationComponent.builder()
.appModules(new AppModules(this))
.build();
}
return component;
}
}
If you perform the above changes, I tend to believe that you'll be all right.
BTW, I recently completed a blog post about Dagger 2 scopes. You might want to check it if you are going to be serious about dependency injection.

Android: Dagger 2 and constructor injection

I'm new to dagger and my constructor/method injection doesn't seem to work.
Doesn't work meaning the injected fields are still requested.
This is my code:
#Module
public class AppContextModule {
private final Context appContext;
public AppContextModule(Context appContext) {
this.appContext = appContext;
}
#Singleton
#Provides
public Context getAppContext() {
return appContext;
}
}
#Singleton
#Component(modules = {AppContextModule.class})
public interface MyComponent {
void inject(ActivitiesLifeCycleListener obj);
void inject(WebViewManager obj);
Context context();
}
public final class MyClass {
private final WeakReference<Context> mAppContext;
#Inject
public MyClass(Context context) {
this.mAppContext = context
}
}
public class MyActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyClass my = new MyClass(); // The IDE requests context!
}
}
"MyClass" still requires "Context" although I used the "#Inject" annotation above the constructor.
What am I missing?
Do I even need to expose my Context?
Thanks.
Well...you declare your constructor
public MyClass(Context context) {
this.mAppContext = context
}
And then call it without any parameters
MyClass my = new MyClass(); // The IDE requests context!
Why do you think this should work? This is plain java, no dagger involved, and you are trying to create some new object without supplying the arguments needed.
Dagger is no magic that changes how java works. To use constructor injection you have to actually use Dagger to inject your fields.
public class MyActivity extends BaseActivity {
#Inject
MyClass mMyClass; // mark field for injection
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// todo ... create component
component.inject(this); // inject fields
}
}
Given that you provide a Context somewhere (you'll get a Context must be provided... compile error if it is not) dagger will then inject your activity with MyClass by calling inject—no magic. The inject method will assign the object to your field.
Constructor injection only means that you don't have to use a module and include a method to provide the object. It does not just magically create objects out of thin air or change the way constructors work.

Dagger's dependency injection ignored, field remains null

I have 2 modules: AndroidModule for providing Context-related objects and PhotosModule providing PhotoManager.
I want to use PhotoManager in PhotosFragment. PhotoManager depends on DbManager, which is provided by AndroidModule.
How do I connect all the dots?
The code compiles, but a NPE is thrown on this line:
mDbManager.readDatabase();
meaning that the injection of DbManager into PhotoManager did not occur, even though I set the
injects = PhotoManager.class
in AndroidModule.
Also, mDbManager is not a private field, so I don't think I have to use injection on PhotoManager's constructor; field injection should be sufficient.
What's the reason and how do I satisfy the missing dependency? I assume there may be more than one problem with my code. Note: I just started with Dagger, please keep that in mind.
Application:
public class TestApp extends Application {
private ObjectGraph mObjectGraph;
#Override
public void onCreate() {
super.onCreate();
mObjectGraph = ObjectGraph.create(getModules().toArray());
}
protected List<Object> getModules() {
return Arrays.asList(new AndroidModule(this), new PhotosModule());
}
public void inject(Object object) {
mObjectGraph.inject(object);
}
}
AndroidModule:
#Module(library = true, injects = PhotoManager.class)
public class AndroidModule {
private final TestApp mApplication;
public AndroidModule(TestApp application) {
mApplication = application;
}
#Provides
#Singleton
#ForApplication
Context provideApplicationContext() {
return mApplication;
}
#Provides
#Singleton
DbManager provideDbManager() {
return new DbManager(mApplication);
}
}
PhotosModule:
#Module(injects = PhotosFragment.class)
public class PhotosModule {
#Provides
#Singleton
PhotoManager providePhotoManager() {
return new PhotoManager();
}
}
PhotoManager:
#Singleton
public class PhotoManager {
#Inject
DbManager mDbManager;
public void doSomething() {
mDbManager.readDatabase();
}
}
PhotosFragment:
public class PhotosFragment extends Fragment {
#Inject
PhotoManager mPhotoManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((TestApp) getActivity().getApplication()).inject(this);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
///...
mPhotoManager.doSomething();
}
}
DbManager:
#Singleton
public class DbManager {
public DbManager(Context context) {
//...
}
public void readDatabase() {
//...
}
}
Here's the core of the problem: in PhotosModule, you have the provider method:
PhotoManager providePhotoManager() {
return new PhotoManager();
}
You are newing up the PhotoManager yourself, so Dagger doesn't have an opportunity to inject its dependency (the DbManager).
I think it's because you're misunderstanding the meaning of the injects property on the #Module(injects=...) annotation of the AndroidModule. You have:
#Module(library = true, injects = PhotoManager.class)
But that injects isn't needed, because you're never calling objectGraph.inject on the PhotoManager. Instead, you're only injecting the PhotoFragment, which is correct...
So one way to fix the NPE problem:
remove the injects=PhotoManager.class from AndroidModule.java:
#Module(library = true)
change PhotosModule to include AndroidModule:
#Module(injects = PhotosFragment.class, includes = AndroidModule.class)
I avoid field injection using the #Inject annotation (only use it for the top-level objects, i.e. where you do the objectgraph.inject, such as activity and fragment level), and use constructor injection instead for all the other dependencies. So I'd change PhotoManager to inject the DbManager like this:
private final DbManager mDbManager;
public PhotoManager(DbManager dbManager) {
mDbManager = dbManager;
}
I've put up the code here on GitHub.

Injecting field into module using dagger

I am trying to inject my android context from one module into another. This is my code so far:
UserProfileModule.java
#Module(
library = true
)
public class UserProfileModule {
#Inject Context _context;
#Provides
public AccountUtils.UserProfile provideUserProfile() {
return AccountUtils.getUserProfile(_context);
}
}
RootModule.java
#Module(
injects = {
PizzaApplication.class,
UserProfileModule.class,
MainActivity.class
},
includes = {
UserProfileModule.class
},
library = true
)
public class RootModule {
private final Context _context;
public RootModule(Context context) {
_context = context;
}
#Provides
#Singleton
public Context provideApplicationContext() {
return _context;
}
}
Anytime it tries to get the user profile it fails saying the object is null.]
EDIT:
PizzaApplication.java
public class PizzaApplication extends Application {
private ObjectGraph objectGraph;
#Override
public void onCreate() {
super.onCreate();
injectDependencies();
}
private void injectDependencies() {
objectGraph = ObjectGraph.create(new RootModule(this));
objectGraph.inject(this);
}
public void inject(Object object) {
objectGraph.inject(object);
}
}
MainActivity.java
public class MainActivity extends BaseActivity {
#InjectView(R.id.toolbar) public Toolbar _toolbar;
#InjectView(R.id.drawer) public DrawerFrameLayout _drawer;
#Inject public AccountUtils.UserProfile _profile;
#Inject public Context _context;
// private NavigationDrawerFragment navigationDrawerFragment;
#Override
protected void onCreate(Bundle saveInstanceState) {
tl;dr (Short version):
For a minimal working example of your code, see my code on GitHub
You don't need to / should't inject the Context from one module into another.
The order/direction of your module includes is reversed, the UserProfileModule should include the RootModule.
More details and comments on your code:
You don't need to inject something into a module, a module only provides dependencies. In your case simply making use of the module includes gives the functionality you want.
Remove the library = true from UserProfileModule, because you only need this when the module's providers aren't all used directly by the classes specified in the injects list.
As Niek Haarman said, you need to pass both RootModule and UserProfileModule instances to ObjectGraph.create in your PizzaApplication's onCreate.
You're doing inject(this) in PizzaApplication but it's got no dependencies, so the inject isn't necessary. Based on the sample code you've provided, this makes me think you're assuming that injecting on the Application-level will also inject Activity dependencies...? You need to do the inject on your Activity too.
You don't show if you're doing an inject in your Activity's onCreate -- that's most likely what's missing.
You're injecting Context into the Activity but that's not necessary since you can just use getApplicationContext() in the Activity.
Here's the working code:
RootModule:
#Module(
injects = {MainActivity.class},
library = true,
complete = false
)
public class RootModule {
private final Context _context;
public RootModule(Context context) {
_context = context;
}
#Provides
#Singleton
public Context provideApplicationContext() {
return _context;
}
}
UserProfileModule:
#Module(includes = {RootModule.class})
public class UserProfileModule {
#Provides
public AccountUtils.UserProfile provideUserProfile(Context context) {
return AccountUtils.getUserProfile(context);
}
}
MainActivity:
public class MainActivity extends BaseActivity {
#Inject
public AccountUtils.UserProfile _profile;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((PizzaApplication) getApplication()).inject(this);
_profile.message();
}
PizzaApplication:
public class PizzaApplication extends Application {
private ObjectGraph objectGraph;
#Override
public void onCreate() {
super.onCreate();
objectGraph = ObjectGraph.create(new RootModule(this), new UserProfileModule());
}
public void inject(Object object) {
objectGraph.inject(object);
}
}
You have
objectGraph = ObjectGraph.create(new RootModule(this));
But, includes = { UserProfileModule.class } is no magic. You have to create it yourself:
objectGraph = ObjectGraph.create(new RootModule(this), new UserProfileModule() );
Furthermore, the #Inject annotation on the Context doesn't work. You're better off modifying your provider method:
#Provides
public AccountUtils.UserProfile provideUserProfile(Context context) {
return AccountUtils.getUserProfile(context);
}
I cannot guarantee this will solve all your problems, but it will help you in the right direction.

How to pass a service to an EmptyInterceptor class?

public class Auditing extends EmptyInterceptor {
#Resource
private ApplicationService applicationService;
public boolean onFlushDirty(Object entity, Serializable id,Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
if(applicationService == null){
System.out.println("applicationService is null");
}
return(true);
}
}
I observed for each hibernate action a new Auditing object is getting created and in onFlushDirty() it is always printing " applicationService is null "
Can anyone tell me how can i make ApplicationService available in an EmptyInterceptor?
I found a work around for this by having a Class with static applicationService property.
Now by creating a static reference for it i can use applicationService.
Its working for now.. Please post any better or efficient suggestions if you have :)
Regards,
You can create a class with application context object by implementing ApplicationContextAware interface.
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ctx) {
context = ctx;
}
}
Now you can obtain your bean using below code.
applicationService = (ApplicationService ) ApplicationContextProvider.getApplicationContext().getBean("applicationService")
Above code can be used to obtain any other bean in future and would not require you to keep adding static beans.

Categories

Resources