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);
}
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();
}
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 don't know why loginResponseHandler of MainRankPresenter.java injected by dagger2 is null in MainRankPresenter.
I just want to inject to field for field injection.
Should I do other way instead field injection?
please, Let me know how to resolve it.
BBBApplication.java
public class BBBApplication extends MultiDexApplication
{
...
#Override
public void onCreate() {
super.onCreate();
initAppComponent();
}
private void initAppComponent() {
this.appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
public static BBBApplication get(Context ctx) {
return (BBBApplication) ctx.getApplicationContext();
}
public AppComponent getAppComponent() {
return this.appComponent;
}
...
}
AppModule.java
#Module
public class AppModule {
private BBBApplication application;
public AppModule(BBBApplication application) {
this.application = application;
}
#Provides
#Singleton
public Application provideApplication() {
return this.application;
}
#Provides
#Singleton
public Resources provideResources() {
return this.application.getResources();
}
#Provides`enter code here`
#Singleton
public SharedPreferences provideSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(this.application);
}
}
AppComponent.java
#Singleton
#Component(modules = {AppModule.class, ServiceModule.class})
public interface AppComponent {
RankFragmentComponent plus(RankFragmentModule module);
Application application();
Resources resources();
}
RankFragmentModule.java
#Module
public class RankFragmentModule {
private RankFragment rankFragment;
public RankFragmentModule(RankFragment rankFragment) {
this.rankFragment = rankFragment;
}
#Provides
#ActivityScope
public LoginResponseHandler provideLoginResponseHandler() {
return new LoginResponseHandler(this.rankFragment);
}
#Provides
#ActivityScope
// #Named("rankFragment")
public RankFragment provideRankFragment() {
return this.rankFragment;
}
#Provides
#ActivityScope
public MainRankPresenter provideMainRankPresenter(RankFragment rankFragment) {
return new MainRankPresenter(new MainRankViewOps(rankFragment));
}
}
RankFragmentComponent.java
#ActivityScope
#Subcomponent(modules = {RankFragmentModule.class})
public interface RankFragmentComponent {
void inject(RankFragment rankFragment);
}
RankFragment.java
public class RankFragment extends Fragment {
#Inject
MainRankPresenter presenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BBBApplication.get(getContext())
.getAppComponent()
.plus(new RankFragmentModule(this))
.inject(this);
presenter.test();
}
MainRankPresenter.java
public class MainRankPresenter implements Presenter {
private MainRankViewOps viewOps;
#Inject
LoginResponseHandler loginResponseHandler;
#Inject
public MainRankPresenter(MainRankViewOps viewOps) {
this.viewOps = viewOps;
}
#Override
public void test() {
Log.d(Constants.TAG_DEBUG, "=== presenter test" + this.toString());
Log.d(Constants.TAG_DEBUG, "=== " + loginResponseHandler.toString());
this.viewOps.initViewOps();
}
}
MainRankViewOps.java
public class MainRankViewOps implements ViewOps {
RankFragment fragment;
#Inject
public MainRankViewOps(RankFragment fragment) {
this.fragment = fragment;
}
#Override
public void initViewOps() {
Log.d(Constants.TAG_DEBUG, "=== view ops" + this.toString());
Log.d(Constants.TAG_DEBUG, "=== " + fragment.toString());
}
}
Injection by Dagger 2 is not recursive. Therefore, when you call inject(this) in RankFragment only #Inject annotated fields of that fragment are being injected. Dagger 2 will not search for #Inject annotations in the injected objects.
In general, you should attempt to restrict usage of Dependency Injection frameworks to "top-level" components (Activities, Fragments, Services, etc.) which are being instantiated by Android framework for you. In objects that you instantiate yourself (like MainRankPresenter) you should use other DI techniques which do not involve external framework (e.g. dependency injection into constructor).
Because you #Provides the MainRankPresenter, Dagger won't inject it: you take responsibility for this. You could possibly have your provides method be passed a MembersInjector si you can inject the fields if the object before returning it, but it'd probably be better to refactor your module you remove that provides method and let Dagger handle the injection (you have all the #Inject needed already)
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
I would really like to implement Dagger in my app but I have a hard time to understand how it should be configured correctly.
I have two classes that I would like to inject.
public class LoginController {
#Inject Bus bus;
#Inject Context context;
// more code
}
public class LoginFragment {
#Inject Bus bus;
// more code
}
If I put all my code in one module it works fine:
#Module(injects = {LoginController.class, LoginFragment.class})
public class BasicModule {
#Provides
#Singleton
public Bus busProvider() {
return new Bus();
}
#Provides
public Context provideContext() {
return RblMobileApp.getContext();
}
}
But when I put those methods in two different modules (BasicModule and TestModule) I get compiler errors:
#Module(injects = {LoginController.class, LoginFragment.class})
public class BasicModule {
#Provides
#Singleton
public Bus busProvider() {
return new Bus();
}
}
Error:(18, 8) java: No injectable members on android.content.Context. Do you want to add an injectable constructor? required by rblmobile.controllers.LoginController for rblmobile.injection.BasicModule
#Module(injects = {LoginController.class})
public class TestModule {
#Provides
public Context provideContext() {
return RblMobileApp.getContext();
}
}
Error:(13, 8) java: No injectable members on com.squareup.otto.Bus. Do you want to add an injectable constructor? required by rblmobile.controllers.LoginController for rblmobile.injection.TestModule
So basically I'm told that BasicModule doesn't deliever Context and TestModule doesn't deliever Bus. Why does the ObjectGraph not know that the respective Constructor is in the other module?
Thanks
EDIT:
That's the method where the ObjectGraph gets created.
public static void init(final Object... modules) {
Log.i(TAG, "initializing object graph");
if (objectGraph == null) {
objectGraph = ObjectGraph.create(modules);
} else {
objectGraph = objectGraph.plus(modules);
}
}
There is a #Module annotation attribute called complete. Try to set it to false:
#Module(complete = false, injects = {LoginController.class, LoginFragment.class})
public class BasicModule {
#Provides
#Singleton
public Bus busProvider() {
return new Bus();
}
}
#Module(complete = false, injects = {LoginController.class})
public class TestModule {
#Provides
public Context provideContext() {
return RblMobileApp.getContext();
}
}