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();
}
Related
#Singleton
public class AppPreferenceHelper implements PreferenceHelper {
static final String PREFS_APP_STATE = "prefsAppState";
static final String APP_STATE_LOGIN = "logIn";
String PREF_NAME ="appPreference" ;
private SharedPreferences sharedPreferences;
#Inject
AppPreferenceHelper(#ApplicationContext Context context) {
this.sharedPreferences = context.getSharedPreferences(PreferenceConstant.PREF_NAME,Context.MODE_PRIVATE);
}
#Override
public void setAppState(String state) {
sharedPreferences.edit().putString(PREFS_APP_STATE,state);
}
#Override
public String getAppState() {
return sharedPreferences.getString(PREFS_APP_STATE,APP_STATE_LOGIN);
}
}
#PerActivity
#Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
void inject(SplashActivity mainActivity);
void inject(LogInActivity logInActivity);
}
#Module
public class ApplicationModule {
private final Application mApplication;
public ApplicationModule(Application application) {
mApplication = application;
}
#Provides
#ApplicationContext
Context provideContext() {
return mApplication;
}
#Provides
Application provideApplication() {
return mApplication;
}
#Provides
#Singleton
AppPreferenceHelper providePreferencesHelper(AppPreferenceHelper appPreferencesHelper) {
return appPreferencesHelper;
}
}
#Module
public class ActivityModule {
private Activity mActivity;
public ActivityModule(Activity activity) {
mActivity = activity;
}
#Provides
#ActivityContext
Context provideContext() {
return mActivity;
}
#Provides
Activity provideActivity`enter code here`() {
return mActivity;
}`enter code here`
#Provides
#PerActivity
SpashMvpPresenter<SplashView> provideSplashPresenter(
SplashPresenter<SplashView> presenter) {
return presenter;
}
}
Error:(20, 10) error: com.d2u.android.data.preference.PreferenceHelper
cannot be provided without an #Provides-annotated method.
com.d2u.android.data.preference.PreferenceHelper is injected at
com.d2u.android.ui.splash.SplashPresenter.(preferenceHelper)
com.d2u.android.ui.splash.SplashPresenter
is injected at
com.d2u.android.di.module.ActivityModule.provideSplashPresenter(presenter)
com.d2u.android.ui.splash.SpashMvpPresenter
is injected at com.d2u.android.ui.splash.SplashActivity.mPresenter
com.d2u.android.ui.splash.SplashActivity is injected at
com.d2u.android.di.component.ActivityComponent.inject(mainActivity)
probably because you are injecting a PreferenceHelper, but your #Provides annotated method returns AppPreferenceHelper instead of PreferenceHelper
#Provides
PreferenceHelper providePreferencesHelper(AppPreferenceHelper appPreferencesHelper) {
return appPreferencesHelper;
}
Differently you could use #Binds
#Binds
abstract PreferenceHelper providePreferencesHelper(AppPreferenceHelper appPreferencesHelper);
for that you are gonna need a different abstract #Module, tho
I am confused about Dagger2 in Android.
I use two scope. #Singleton, #PerActivity
This is my Code. I simplyfy my code.
//ApplicationComponent.java
#Singleton
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
#Named("packageName") String packageName();
}
//ApplicationModule.java
#Module
public class ApplicationModule {
#Provides
#Singleton
public Context provideApplicationContext() {
return MyApplication.getContext();
}
#Provides
#Singleton
#Named("packageName")
public String providePackageName(Context context) {
return context.getPackageName();
}
}
//UserComponent.java
#PerActivity
#Component(modules = {UserModule.class})
public interface UserComponent {
void inject(MainActivity activity);
}
//UserModule.java
#Module
public class UserModule {
String packageName;
public UserModule(String packageName) {
this.packageName = packageName;
}
#Provides
#PerActivity
UserRepositoryImpl provideUserRepositoryImpl() {
return new UserRepositoryImpl(packageName);
}
}
for inject appVersion, packagename in UserModule
DaggerChatComponent.builder()
.userModule(new UserModule(getApplicationComponent().packageName()))
.build();
but it looks not great. how can i inject when use different Scope??
your ApplicationModule.java is correct
#Module
public class ApplicationModule {
private Application application;
public ApplicationModule(Application application){
this.application = application;
}
#Provides
#Singleton
Context provideContext(){
return application;
}
#Provides
#Singleton
#Named("packagename")
public String providePackageName(Context context) {
return context.getPackageName();
}
}
and it's component class is also right ApplicationComponent.java
#Singleton
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
#Named("packagename") String providepackagename();
}
but in the UserModule.java you need not pass the package name object , dagger's object graph does this for you.
#Module
public class UserModule {
public UserModule() {
}
#Provides
#PerActivity
UserRepositoryImpl provideUserRepositoryImpl(#Named("packagename") String packageName) {
return new UserRepositoryImpl(packageName);
}
}
and the next step is while writing the component class for this module add the application component as a dependency ie, your UserComponent.java looks like this
#PerActivity
#Component(dependencies = {ApplicationComponent.class},modules = {UserModule.class})
public interface UserComponent {
void inject(MainActivity mainActivity);
}
with the activity scope as PerActivity.lava
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface PerActivity {
}
with this example UserRepositoryImpl.java as
class UserRepositoryImpl {
private String packagename;
public UserRepositoryImpl(String packagename){
this.packagename = packagename;
}
String getPackagename(){
return packagename;
}
}
you can finally inject this in your activity.(MainActivity.java)
public class MainActivity extends AppCompatActivity {
#Inject
UserRepositoryImpl userRepository;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ApplicationComponent component=DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(getApplication())).build();
UserComponent userComponent=DaggerUserComponent.builder().applicationComponent(component).userModule(new UserModule()).build();
userComponent.inject(this);
Log.e("name"," "+userRepository.getPackagename());
}
}
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 am trying my hands on Dependancy Injection using Dagger2.
It gives error in build phase and says cannot inject SharedPreference instance.
Here are my modules and components.
Application Module
#Module
public class ApplicationModule {
private Application app;
private String PREF_NAME = "prefs";
public ApplicationModule(Application app) {
this.app = app;
}
#Singleton
#Provides
public Picasso getPicasso() {
return new Picasso.Builder(app).build();
}
#Singleton
#Provides
public SharedPreferences getAppPreferences() {
return app.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
}
ApplicationComponent
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
void inject(App app);
}
App.java
public class App extends Application {
ApplicationComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
createDaggerInjections();
}
public ApplicationComponent getAppComponent() {
return appComponent;
}
public static App getAppInstance(Context context) {
return (App) context.getApplicationContext();
}
private void createDaggerInjections() {
appComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
appComponent.inject(this);
}
}
Login Module
#Module
public class LoginModule {
LoginView view;
public LoginModule(LoginView view) {
this.view = view;
}
#Provides LoginView getLoginView()
{
return view;
}
#Provides LoginPresenter getLoginPresenter(LoginView view)
{
return new LoginPresenterImpl(view);
}
}
LoginComponent
#ActivityScope
#Component(
dependencies = ApplicationComponent.class,
modules = LoginModule.class)
public interface LoginComponent {
void inject(LoginActivity activity);
LoginPresenter getLoginPresenter();
}
LoginActivity.java
public class LoginActivity extends BaseActivity implements LoginView {
private static final String TAG = "LoginActivity";
#Inject
SharedPreferences prefs;
-----
-----
-----
#Override
public void createDaggerInjections() {
DaggerLoginComponent.builder().applicationComponent(App.getAppInstance(this).getAppComponent())
.loginModule(new LoginModule(this))
.build();
}
This line #Inject
SharedPreferences prefs; gives error which is as follows. The same error comes when I try to inject Picasso Instance also.
/home/blackidn/proj/styling android 3/dagger 2/DaggerEx/app/src/main/java/com/mohammad/daggerex/App/App.java:8: error: cannot find symbol
import com.mohammad.daggerex.dagger.DaggerApplicationComponent;
^
symbol: class DaggerApplicationComponent
location: package com.mohammad.daggerex.dagger
/home/blackidn/proj/styling android 3/dagger 2/DaggerEx/app/src/main/java/com/mohammad/daggerex/ui/Login/LoginComponent.java:16: error: android.content.SharedPreferences cannot be provided without an #Provides- or #Produces-annotated method.
void inject(LoginActivity activity);
^
com.mohammad.daggerex.ui.Login.LoginActivity.prefs
[injected field of type: android.content.SharedPreferences prefs]
Stucked with this and Don't know how to solve this and move forward. What am I missing ? Any help would be great.
You should expose SharedPreferences to LoginComponent in ApplicationComponent. Otherwise, you can't inject it.
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
void inject(App app);
SharedPreferences sharedPreferences();
}
Dagger 2 update :
The #Subcomponent could be used without any need to do this as can be seen here for the context: (ChatComponent is a subcomponent with custom scope)
from this excellent article:
https://proandroiddev.com/dagger-2-part-ii-custom-scopes-component-dependencies-subcomponents-697c1fa1cfc