So I am trying to get my head around Dagger2 and it's dependency injection. Here is my current setup.
SQLiteModelModule
#Module
public class SQLiteModelModule {
private final int versionNumber;
private final Context context;
private final String databaseName;
public SQLiteModelModule( Context context, String databaseName, int versionNumber ) {
this.context = context;
this.databaseName = databaseName;
this.versionNumber = versionNumber;
}
#Provides
#Singleton
public SQLite providesSQLite() {
return SQLite.newInstance(context,databaseName,versionNumber);
}
#Provides
#Singleton
public SQLiteModel providesSQLiteModel( SQLite sqlite) {
return new SQLiteModel( sqlite );
}
}
Because this is a library and the ModelManager may contain different modules other than the SQLiteModel I have created a TestModelManagerModule to provide the manager.
TestModelManagerModule
#Module
public class TestModelManagerModule {
#Provides
#Singleton
public ModelManager providesModelManager( SQLiteModel model ) {
ModelManager manager = new ModelManager();
manager.registerModel(model,"sosmv1",true);
return manager;
}
}
Then to tie them all together for my tests I have a test component.
SQLiteTestComponent
#Singleton
#Component( modules = {TestModelManagerModule.class, SQLiteModelModule.class})
public interface SQLiteTestComponent {
ModelManager provideModelManager();
}
This works when I do the following and the correct type of 'IModel' Object is returned.
private ModelManager modelManager;
private SQLiteTestComponent sqLiteTestComponent;
#Before
public void setup() {
Context context = Mockito.mock(Context.class);
this.sqLiteTestComponent = DaggerSQLiteTestComponent.builder()
.sQLiteModelModule(new SQLiteModelModule(context, "TestDB", 1))
.build();
this.modelManager = sqLiteTestComponent.provideModelManager();
}
#Test
public void testCreation() {
IModel model = modelManager.getDefaultModel();
Assert.assertEquals(SQLiteModel.class, model.getClass());
}
MY QUESTION
I have a class OMKObject that has a dependency on ModelManager. It uses this dependency not only in constructors but also in a field. How do I go about injecting this?
Also is the above all correct? Or have I done something wrong in the setup?
Related
I would like to implement Repository module to handle data operations. I have JSON file in row directory and want create concrete Repository implementation to get data from file. I'm not sure if I can use Context as attribute in the constructor or method of Repository.
e.g.
public class UserRepository {
UserRepository() {}
public List<User> loadUserFromFile(Context contex) {
return parseResource(context, R.raw.users);
}
}
IMHO, you should use DI (Dependency Injection) like Dagger2, to provide you Context something like,
AppModule.class
#Module
public class AppModule {
private Context context;
public AppModule(#NonNull Context context) {
this.context = context;
}
#Singleton
#Provides
#NonNull
public Context provideContext(){
return context;
}
}
MyApplication.class
public class MyApplication extends Application {
private static AppComponent appComponent;
public static AppComponent getAppComponent() {
return appComponent;
}
#Override
public void onCreate() {
super.onCreate();
appComponent = buildComponent();
}
public AppComponent buildComponent(){
return DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
}
UserRepository.class
#Singleton
public class UserRepository {
UserRepository() {}
#Inject
public List<User> loadUserFromFile(Context contex) {
return parseResource(context, R.raw.users);
}
}
Happy Coding..!!
I don't see any harm in passing the context as an attribute. If you dislike the idea then you can retrieve the context via a convenient method : Static way to get 'Context' on Android?
I'm learning Dagger 2 and am working on an app. I have a settings module which depends on a settings manager which depends on a shared preferences manager. My problem is that my Settings Module is not getting injected with a settings manager before it itself is being called. That settings manager needs a SharedPrefsManager which is also not being injected anywhere.
What am I doing wrong?
Snippets in order of dependency:
#Module
public class SettingsModule {
#Inject SettingsManager manager;
#Provides
#TimeControl
int[] provideResetTime(){
return manager.getResetTime();
}
#Provides
#ThemeControl
int provideThemeID(){
return manager.getTheme();
}
}
Depends on Settings Manager:
public class SettingsManager{
private SharedPreferencesManager manager;
#Inject
SettingsManager(SharedPreferencesManager manager){
this.manager = manager;
}
}
Depends on Shared prefs manager:
public class SharedPreferencesManager {
private static SharedPreferencesManager instance = null;
public static SharedPreferencesManager getInstance(){return instance;}
String prefsKey = "SHAREDPREFSKEY";
SharedPreferences sharedPrefs = null;
Context applicationContext = null;
#Inject
SharedPreferencesManager(#ApplicationContext Context applicationContext){
this.prefsKey = prefsKey;
this.applicationContext = applicationContext;
sharedPrefs = applicationContext.getSharedPreferences(prefsKey,Context.MODE_PRIVATE);
instance = this;
}
}
#Module
public class SettingsModule {
#Inject SettingsManager manager;
#Provides
#TimeControl
int[] provideResetTime(){
return manager.getResetTime();
}
#Provides
#ThemeControl
int provideThemeID(){
return manager.getTheme();
}
}
Should be
#Module
public class SettingsModule {
#Provides
#TimeControl
int[] resetTime(SettingsManager manager) {
return manager.getResetTime();
}
#Provides
#ThemeControl
int themeId(SettingsManager manager) {
return manager.getTheme();
}
}
Beware that your providers aren't scoped, so (AFAIK) a call that obtains themeId() and a call that obtains resetTime() will most likely create a new SettingsManager each time.
So you might want to put #Singleton on your provided classes.
#Singleton
public class SharedPreferencesManager {
private SharedPreferences sharedPrefs = null;
String prefsKey = "SHAREDPREFSKEY";
Context applicationContext = null;
#Inject
SharedPreferencesManager(Context applicationContext) {
this.applicationContext = applicationContext;
sharedPrefs = applicationContext.getSharedPreferences(prefsKey, Context.MODE_PRIVATE); // why isn' this in a module?
}
}
#Singleton
public class SettingsManager{
private SharedPreferencesManager manager;
#Inject
SettingsManager(SharedPreferencesManager manager){
this.manager = manager;
}
}
I don't think you should have #Inject annotations in the modules since they are built to be the ones that create the dependencies and only receive other ones through the object graph or a simple constructor.
Here's an example on how you could avoid that #Inject annotation in the Module and the constructor injectors after it.
SettingsModule.java
#Module
public class SettingsModule {
#Provides
#TimeControl
int[] provideResetTime(SettingsManager manager) {
return manager.getResetTime();
}
#Provides
#ThemeControl
int provideThemeID(SettingsManager manager) {
return manager.getTheme();
}
#Provides
SettingsManager provideSettingsManager(SharedPreferencesManager sharedPreferencesManager) {
return new SettingsManager(sharedPreferencesManager);
}
#Provides
SharedPreferencesManager provideSharedPreferencesManager(#ApplicationContext Context context) {
return new SharedPreferencesManager(context);
}
}
SettingsManager.java
public class SettingsManager {
private SharedPreferencesManager manager;
SettingsManager(SharedPreferencesManager manager) {
this.manager = manager;
}
}
SharedPreferencesManager.java
public class SharedPreferencesManager {
private static SharedPreferencesManager instance = null;
private SharedPreferences sharedPrefs = null;
String prefsKey = "SHAREDPREFSKEY";
Context applicationContext = null;
SharedPreferencesManager(Context applicationContext) {
this.applicationContext = applicationContext;
sharedPrefs = applicationContext.getSharedPreferences(prefsKey, Context.MODE_PRIVATE);
instance = this;
}
public static SharedPreferencesManager getInstance() {
return instance;
}
}
With this, you would leave all your injection logic to your Module, and the concrete classes won't have to worry about injecting the classes themselves.
I'm trying to refactor my app using MVP pattern and Dagger 2 for Dependency Injection.
I Create module that provides Application Context and I want get Context to get SharedPreferences on Model Layer.
I inject Context on Presenter layer and it's working with SharedPreference, but when I move to Model layer, Dagger inject a null value on Context variable.
Inject
#Inject
public Context mContext;
App Module
AppModule provides Application Context
#Module
public class AppModule {
private App app;
public AppModule(App app){
this.app = app;
}
#Provides
public Context providesApp(){
return app.getApplicationContext();
}
}
Application
public class App extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.mainModule(new MainModule())
.build();
}
public AppComponent getAppComponent(){
return appComponent;
}
}
App Component
#Component(modules = {AppModule.class,MainModule.class})
public interface AppComponent {
void inject(MainActivity activity);
void inject(LoginActivity activity);
}
Main Module
#Module
public class MainModule {
#Provides
public MainContract.Model providesMainModel(){
return new MainModel();
}
#Provides
public LoginContract.Model providesLoginModel(){
return new LoginModel();
}
}
Since you have the Context in your App Module, you can add the Context as parameter to LoginModel constructor.
#Provides
public LoginContract.Model providesLoginModel(Context contect){
return new LoginModel(contect);
}
#Provides
public LoginContract.Presenter providesLoginPresenter(LoginContract.Model model){
return new LoginPresenter(model);
}
But bear in mind that you must use android packages only on your Activities and Fragments for easy testing. So your approach is wrong.
EDIT
Well for me, the best option is to create a class for SharedPreferences like this
public class Preferences {
private final SharedPreferences preferences;
private String key;
private String defaultValue;
public StringPreferences(#NonNull SharedPreferences preferences) {
this.preferences = preferences;
}
#Nullable
public String get(String mykey) {
return preferences.getString(mykey, defaultValue);
}
public void set(#NonNull String mykey, #Nullable String value) {
preferences.edit().putString(mykey, value).apply();
}
// same way for integers, longs, doubles and booleans
public boolean isSet() {
return preferences.contains(key);
}
public void delete() {
preferences.edit().remove(key).apply();
}
}
Then in App Module I Provide the SharedPreferences
#Provides
#Singleton
public SharedPreferences provideSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
#Provides
#Singleton
public Preferences providePreferences(SharedPreferences prefs) {
return new Preferences(prefs);
}
Finally in Main Module
#Provides
public LoginContract.Model providesLoginModel(Preferences prefs){
return new LoginModel(prefs);
}
#Provides
public LoginContract.Presenter providesLoginPresenter(LoginContract.Model model){
return new LoginPresenter(model);
}
I'm newbie to use Dagger2 on android. i create some class as Dagger module which they are using context, i can't merge,combine or using single context for other modules which they need that. and i get this error now:
android.content.Context is bound multiple times
SpModules:
#Module
public class SpModules {
private Context context;
public SpModules(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
}
}
RealmModule:
#Module
public class RealmModule {
private Context context;
#Provides
Context provideApplicationContext() {
return AlachiqApplication.getInstance();
}
#Provides
#Singleton
RealmConfiguration provideRealmConfiguration() {
final RealmConfiguration.Builder builder = new RealmConfiguration.Builder()
.schemaVersion(Migration.SCHEMA_VERSION)
.deleteRealmIfMigrationNeeded()
.migration(new Migration());
return builder.build();
}
#Provides
Realm provideDefaultRealm(RealmConfiguration config) {
return Realm.getInstance(config);
}
#Provides
Context provideContext() {
return this.context;
}
}
Component:
#Component(modules = {RealmModule.class, SpModules.class})
#Singleton
public interface ApplicationComponent {
void inject(ActivityRegister target);
void inject(ActivityMain target);
void inject(ActivityBase target);
void inject(FragmentAlachiqChannels target);
void inject(SocketServiceProvider target);
}
and then Application class to make Dagger2:
component = DaggerApplicationComponent.builder()
.appModules(new SpModules(this))
.build();
how can i resolve this problem?
That's simply dagger telling you that you're providing several times the same class to your component. In fact you do it here as well as across different modules for the same component:
#Provides
Context provideApplicationContext() {
return AlachiqApplication.getInstance();
}
#Provides
Context provideContext() {
return this.context;
}
These methods are different, but they're providing the same dependency and without extra info dagger is not able to determine which Context to use.
You got a couple of options. If a single context would be enough to serve all your classes, then you could simply remove the extra provide methods.
However, let's say you need both application and some other context. You can use the annotation Named. Here's how it works, you annotate your provide methods with this annotation which basically will give a name to the dependency. Like so:
#Module
public class SpModules {
// ...
#Provides
#Named("context")
Context provideContext() {
return this.context;
}
//...
}
#Module
public class RealmModule {
//...
#Provides
#Named("application.context")
Context provideApplicationContext() {
return AlachiqApplication.getInstance();
}
//...
}
Now you need to also annotated when you use these dependencies, for example say you have an object that depends on the application context:
public class Something {
public Something(#Named("application.context") Context context) {
//...
}
}
Or as a field:
public class Something {
#Named("application.context") Context context;
// ...
}
Or even in your module:
#Provides
#Singleton
SP provideSharePreferences(#Named("context") Context context) {
return new SP(context);
}
The name can be anything you want as long as they're consistent.
An alternative is to use Qualifiers. They work similar to the Named annotation, but are different. You'll be qualifying the dependency. So say you create 2 qualifiers:
#java.lang.annotation.Documented
#java.lang.annotation.Retention(RUNTIME)
#javax.inject.Qualifier
public #interface InstanceContext {
}
#java.lang.annotation.Documented
#java.lang.annotation.Retention(RUNTIME)
#javax.inject.Qualifier
public #interface ApplicationContext {
}
You can then use these to annotate the provide methods:
#Module
public class SpModules {
// ...
#Provides
#InstanceContext
Context provideContext() {
return this.context;
}
//...
}
#Module
public class RealmModule {
//...
#Provides
#ApplicationContext
Context provideApplicationContext() {
return AlachiqApplication.getInstance();
}
//...
}
You then use it the same way as with name. Here are the examples:
public class Something {
public Something(#ApplicationContext Context context) {
//...
}
}
public class Something {
#ApplicationContext Context context;
// ...
}
#Provides
#Singleton
SP provideSharePreferences(#InstanceContext Context context) {
return new SP(context);
}
Again, these names can be anything. I personally didn't put a lot of effort in thinking about them. Hope this helps.
I have these 2 simple modules modules:
AppModule
#Module(injects =
{
HomeActivity.class,
},
includes = {
GoogleApiModule.class,
DbModule.class,
GcmModule.class,
})
public class AppModule {
private final Application app;
public AppModule(Application app) {
this.app = app;
}
#Provides
#Singleton
Application provideApplication() {
return app;
}
}
ApiModule
#Module(injects =
{
LoginFragment.class,
}, addsTo = AppModule.class)
public class ApiModule {
private final Context context;
public ApiModule(Context context) {
this.context = context;
}
#Provides
#Singleton
Publicapi providePublicApi() {
Publicapi.Builder builder = new
return new Publicapi();
}
}
Beacause of #Inject PublicApi in LoginFragment compiler complains:
No injectable members on myapp.publicapi.Publicapi. Do you want to add an injectable constructor? required by myapp.fragments.LoginFragment for myapp.modules.AppModule
I've been reading several threads I was sure I'm doing it right, been obviously wrong. What is wrong in this setup?
Publicapi must have a constructor method who receive the context, and you have to assign it an context class attribute. I mean:
public Publicapi(Context context) {
this.context = context;
}
Then... in ApiModule you have to instantiate the constructor sending the context as param. Something like that:
// (With "a" in lower case like the class name) !!!
#Provides
#Singleton
Publicapi providesPublicapi(Context context) {
return new Publicapi(context);
}
Regards!