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.
Related
I'm trying to create a Singleton of SharedPreferences using Dagger 2, and I keep getting the following error message:
com.test.app.injection.component.ConfigPersistentComponent scoped with #com.test.app.injection.ConfigPersistent may not reference bindings with different scopes:
#Singleton class com.test.app.data.local.PreferencesHelper
I am trying to access shared preferences via Injected constructor in my MainPresenter. Using a Singleton DataManager for API access works fine via Injected constructor, but not for SharedPreferences.
Here is my setup:
AppModule
#Module(includes = {ApiModule.class})
public class AppModule {
private final Application application;
public AppModule(Application application) {
this.application = application;
}
#Provides
Application provideApplication() {
return application;
}
#Provides
#ApplicationContext
Context provideContext() {
return application;
}
#Provides
#Singleton
#ApplicationContext
SharedPreferences provideSharedPreference(#ApplicationContext Context context) {
return context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
}
}
AppComponent
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
#ApplicationContext
Context context();
Application application();
DataManager apiManager();
#ApplicationContext
SharedPreferences prefManager(Context context);
}
ConfigPersistentComponent
/**
* A dagger component that will live during the lifecycle of an Activity or Fragment but it won't be
* destroy during configuration changes. Check {#link BaseActivity} and {#link BaseFragment} to see
* how this components survives configuration changes. Use the {#link ConfigPersistent} scope to
* annotate dependencies that need to survive configuration changes (for example Presenters).
*/
#ConfigPersistent
#Component(dependencies = AppComponent.class)
public interface ConfigPersistentComponent {
ActivityComponent activityComponent(ActivityModule activityModule);
FragmentComponent fragmentComponent(FragmentModule fragmentModule);
}
ConfigPersistent
/**
* A scoping annotation to permit dependencies conform to the life of the {#link
* ConfigPersistentComponent}
*/
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface ConfigPersistent {
}
PreferencesHelper
#Singleton
public class PreferencesHelper {
private final SharedPreferences preferences;
#Inject
PreferencesHelper(SharedPreferences sharedPreferences) {
preferences = sharedPreferences;
}
public void putString(#Nonnull String key, #Nonnull String value) {
preferences.edit().putString(key, value).apply();
}
public String getString(#Nonnull String key) {
return preferences.getString(key, "");
}
public void putBoolean(#Nonnull String key, #Nonnull boolean value) {
preferences.edit().putBoolean(key, value).apply();
}
public boolean getBoolean(#Nonnull String key) {
return preferences.getBoolean(key, false);
}
public void putInt(#Nonnull String key, #Nonnull boolean value) {
preferences.edit().putBoolean(key, value).apply();
}
public int getInt(#Nonnull String key) {
return preferences.getInt(key, -1);
}
public void clear() {
preferences.edit().clear().apply();
}
}
MainPresenter
#ConfigPersistent
public class MainPresenter extends BasePresenter<MainMvpView> {
private final PreferencesHelper preferencesHelper;
private final DataManager dataManager;
#Inject
public MainPresenter(PreferencesHelper preferencesHelper, DataManager dataManager) {
this.preferencesHelper = preferencesHelper;
this.dataManager = dataManager;
}
...
}
#Provides
#Singleton
// #ApplicationContext // <-- remove this
SharedPreferences provideSharedPreference(#ApplicationContext Context context) {
return context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
}
And add this:
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
...
// #ApplicationContext
SharedPreferences prefManager();
PreferencesHelper preferencesHelper();
}
After searching and trying too many things i'm stuck in some what seems like an easy problem.
Below is my module which is reponsible for injecting retrofit.
#Module
public class NetworkingModule
{
#Provides
public Retrofit providesRetrofit()
{
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return retrofit;
}
}
My NetworkComponent
#Component( modules = NetworkingModule.class)
public interface NetworkingComponent {
void inject(DashboardPresenterImpl target);
void inject(PicksPresenterImpl picksPresenter);
void inject(LoadsPresenterImpl loadsPresenter);
void inject(ShippingPresenterImpl shippingPresenter);
void inject(GeneralFilePresenterImpl generalFilePresenter);
}
A utility class with constructor injection. Please note this class also has injection of AppPreferences.
public class AppUtils {
private Context context;
#Inject
AppPreferences preferences;
#Inject
public AppUtils(#ActivityContext Context context)
{
this.context = context;
/*ActivityComponent component = DaggerActivityComponent.builder()
.activityModule(new ActivityModule((Activity) context))
.build();
component.inject(this);*/
}
}
Now in my Code i want to achieve this
Class MyPresenterImpl{
#Inject
Retrofit retrofit;
#Inject
AppUtils appUtils;
}
Please suggest an optimize and good way to achieve the above.
EDIT
Added AppPreference.java
public class AppPreferences {
#Inject
SharedPreferences sharedPreferences;
private SharedPreferences.Editor editor;
#Inject
public AppPreferences(#ActivityContext Context context)
{
ApplicationComponent component = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule((Application) context.getApplicationContext()))
.build();
component.inject(this);
editor = sharedPreferences.edit();
}
public void putString(String key, String value)
{
editor.putString(key, value).commit();
}
public String getString(String key)
{
return sharedPreferences.getString(key, null);
}
}
Please decide on whether you want to use field injection or constructor injection, and hopefully choose constructor injection.
public class AppUtils {
private Context context;
#Inject // field injection?
AppPreferences preferences;
#Inject // constructor injection?
public AppUtils(#ActivityContext Context context)
{
// ...
}
}
Dagger won't inject your fields if you use constructor injection and you should not call it yourself afterwards either. Your component should really not contain all those methods to inject your presenter etc.
If you need something, put it in the constructor.
public class AppUtils {
private Context context;
private AppPreferences preferences;
#Inject // constructor injection!
public AppUtils(#ActivityContext Context context, AppPreferences preferences)
{
// ...
}
}
The same applies for MyPresenterImpl. If you depend on something, put it in the constructor, mark the constructor with #Inject, and Dagger will create the object for you with all the dependencies provided.
Your components should only contain .inject(..) method for Android framework types (Activities, Fragments, ...) and nothing else.
I also wrote an article recently with some general concepts about the use of Dagger.
my suggestion:
create context module:
#Module
public class ContextModule
{
Context context;
public ContextModule(Context context) {
this.context = context;
}
#Provides
#AppScope
Context context() {
return context;
}
}
create SharedPreferences module
#Module
public class SharedPreferencesModule
{
#Provides
#AppScope
AppSharedPreferences provideAppSharedPreferences(Context context) {
return new AppSharedPreferences(context.getSharedPreferences("App",Context.MODE_PRIVATE));
}
}
same way You can create more modules if you need to (for example for AppUtils)
instead of creating separate component for network, create one for your app
#Component( modules = {ContextModule.class, NetworkingModule.class, SharedPreferencesModule})
public interface AppComponent {...
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);
}
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?
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!