Repository module implementation with Context - android

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?

Related

Dagger 2 interdependency issue

In my application , I am trying to create Dagger 2 components for
Context (To use in different classes) Which is already done
AppUtil (requires context for network check method) Need to get this done!
I have created components and initiated from Application class.
public class MyApp extends Application{
private ContextComponent mContextComponent;
private AppUtilComponent mAppUtilComponent;
private NetworkComponent mNetworkComponent;
#Override
public void onCreate() {
super.onCreate();
mNetworkComponent = createNetworkComponent();
mContextComponent = createContextComponent();
mAppUtilComponent = createAppUtilComponent();
}
private AppUtilComponent createAppUtilComponent() {
return DaggerAppUtilComponent.builder().appUtilModule(new AppUtilModule(this)).build();
}
public AppUtilComponent getAppUtilComponent() {
return mAppUtilComponent;
}
private NetworkComponent createNetworkComponent() {
return DaggerNetworkComponent.builder().networkModule(new NetworkModule()).build();
}
public NetworkComponent getNetworkComponent() {
return mNetworkComponent;
}
private ContextComponent createContextComponent() {
return DaggerContextComponent.builder().contextModule(new ContextModule(this)).build();
}
public ContextComponent getContextComponent(){
return mContextComponent;
}
}
The ContextModule class is as follows
#Module
public class ContextModule {
private Context mContext;
public ContextModule(Context context){
mContext = context;
}
#Provides
#Singleton
Context getContext(){
return mContext;
}
}
Context component will be
#Singleton
#Component (modules = ContextModule.class)
public interface ContextComponent {
void inject(AppUtils appUtils);
}
AppUtilModule is like
#Singleton
#Component (modules = AppUtilModule.class)
public interface AppUtilComponent {
void inject(SplashActivity splashActivity);
}
With this AppUtilModule have modified as
#Module (includes = ContextModule.class)
public class AppUtilModule {
private AppUtils mAppUtils;
private Context context;
#Inject
public AppUtilModule(Context context) {
this.context = context;
}
#Provides
public AppUtils getAppUtil(){
mAppUtils = new AppUtils(context);
return mAppUtils;
}
}
Now my AppUtils is getting context injected , which is perfectly alright.
public class AppUtils {
public Context mContext;
#Inject
public AppUtils(Context _context){
mContext = _context;
}
public boolean isNetworkConnected(){
//Using mContext to determine Network
}
... Other Utility methods which will be used throughout my application
}
But now how can I make AppUtil as a separate Dagger component (which
is internally having Context as a Dependency) and inject in other classes?
EDIT 1: After making constructor injection of context into AppUtils and using AppUtil component in SplashActivity which already had Network component
After making the AppUtil as Dagger dependency, now Project is giving
compile time error. Before this changes, NetworkProcessor in SplashActivity used to work
fine, as it was only the dependency SplashActivity had. What is that I
am missing/ doing wrong!
public class SplashActivity ....
{
#Inject
public NetworkProcessor mNetworkProcessor;
.....
#Inject
public AppUtils mAppUtils;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApp.getInstance().getAppUtilComponent().inject(this);
MyApp.getInstance().getNetworkComponent().inject(this);
}
}
Error:
rror: [Dagger/MissingBinding] com.dev.myapp.networking.NetworkProcessor cannot be provided without an #Inject constructor or an #Provides-annotated method.
com.dev.myapp.networking.NetworkProcessor is injected at
com.dev.myapp.ui.activities.landing.SplashActivity.mNetworkProcessor
com.dev.myapp.ui.activities.landing.SplashActivity is injected at
com.dev.myapp.components.AppUtilComponent.inject(com.dev.myapp.ui.activities.landing.SplashActivity)
So a component can create multiple dependencies at once, which are usually grouped by lifetime scope (Application = whole app lifetime, Activity = activity lifetime).
So if your AppUtils class has the same scope as your ContextComponent, you can just use it to inject the AppUtils into classes that should use it, e.g. an activity:
public class MainActivity extens Activity {
#Inject AppUtils appUtils;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApp) getApplication()).getContextComponent().inject(this);
}
}
after extending the ContextComponent definition to
#Singleton
#Component (modules = ContextModule.class)
public interface ContextComponent {
void inject(AppUtils appUtils);
void inject(MainActivity activity);
}
and you need to change AppUtils to use conastructor injection:
public class AppUtils {
private Context mContext;
#Inject
public AppUtils(Context context){
this.mContext = context;
}
}
After Question Edit 1
Dagger does not know how to create the NetworkProcessor-class, hence the compiler error. To make it work, you should change NetworkProcessor to have a constructor that is annotated with #Inject like you did with AppUtils (the second option would be creating a #Provides method in a dagger module, but the first solution is easier).
You did not supply the source code of NetworkProcessor so I'm assuming here it only needs a Context as a dependency.
Now that both AppUtils and NetworkProcessor have an injectible constructor that only has Context as an argument, Dagger can create the missing links by itself.
You don't need the NetworkComponent and the AppUtilComponent, just one Component, so delete them. Also delete the NetworkModule and the AppUtilModule. ContextComponent now is sufficient to inject all the dependencies into SplashActivity:
public class SplashActivity .... {
#Inject public NetworkProcessor mNetworkProcessor;
.....
#Inject public AppUtils mAppUtils;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApp.getInstance().getContextComponent().inject(this);
}
//...
}
This works because Dagger can create the wiring code itself, since it knows how to instantiate both AppUtils and NetworkProcessor.

How to Inject Retrofit using Dagger2

I'm trying to implement MVP pattern using Dagger2. While I successfully did di for application, activities and for fragments (I'm not sure I did well with fragments). Actually after reading guides I still don't understand how it works.
I created RetrofitModiule:
#Module
public class RetrofitModule {
String mBaseUrl;
...
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(mBaseUrl)
.client(okHttpClient)
.build();
}
Then I declare module in MyApplicationComponent:
#Singleton
#Component(
modules = {
MyApplicationModule.class,
RetrofitModule.class
}
)
public interface MyApplicationComponent {
void inject(MyApplication myApplication);
Retrofit provideRetrofit();
}
Actually I don't understand why I have to use Inject here; Because there nothing to actually inject into MyApplication:
public class MyApplication extends Application {
private MyApplicationComponent mMyApplicationComponent;
...
#Override
public void onCreate() {
super.onCreate();
mMyApplicationComponent = DaggerMyApplicationComponent.builder()
.retrofitModule(new RetrofitModule("https://androidtutorialpoint.com"))
.build();
mMyApplicationComponent.inject(this);
}
}
I use Retrofit only in LoaderActivityPresenterImpl which injected to LoaderActivity;
#ActivityScoped
public class LoaderActivityPresenterImpl implements LoaderActivityPresenter {
private LoaderActivityView mView;
private #ActivityContext Context mContext;
private Retrofit mRetrofit;
#Inject
public LoaderActivityPresenterImpl(LoaderActivityView view, #ActivityContext Context context, Retrofit retrofit) {
mView = view;
mContext = context;
mRetrofit = retrofit;
}
}
LoaderActivity:
public class LoaderActivity extends BaseActivity implements LoaderActivityView{
#Inject LoaderActivityPresenter mPresenter;
private LoaderActivityComponent mLoaderActivityComponent;
#Override
protected void setupActivityComponent(MyApplicationComponent myApplicationComponent) {
mLoaderActivityComponent = DaggerLoaderActivityComponent.builder()
.myApplicationComponent(myApplicationComponent)
.loaderActivityModule(new LoaderActivityModule(this, this, myApplicationComponent.provideRetrofit()))
.build();
mLoaderActivityComponent.inject(this);
}
LoaderComponent:
#ActivityScoped
#Component(
modules = LoaderActivityModule.class,
dependencies = MyApplicationComponent.class
)
public interface LoaderActivityComponent {
void inject(LoaderActivity loaderActivity);
}
LoaderActivityModule:
#Module
public class LoaderActivityModule {
private Retrofit mRetrofit;
private LoaderActivityView mLoaderActivityView;
private #ActivityContext Context mContext;
public LoaderActivityModule(LoaderActivityView loaderActivityView, #ActivityContext Context context, Retrofit retrofit) {
mLoaderActivityView = loaderActivityView;
mContext = context;
mRetrofit = retrofit;
}
#Provides
LoaderActivityView provideLoaderActivityView() {
return mLoaderActivityView;
}
#Provides
public #ActivityContext Context provideActivityContext() {
return mContext;
}
#Provides
public LoaderActivityPresenter LoaderActivityPresenterImpl() {
return new LoaderActivityPresenterImpl(mLoaderActivityView, mContext, mRetrofit);
}
}
LoaderActivityComponent:
#ActivityScoped
#Component(
modules = LoaderActivityModule.class,
dependencies = MyApplicationComponent.class
)
public interface LoaderActivityComponent {
void inject(LoaderActivity loaderActivity);
}
I get this error:
java.lang.RuntimeException: Unable to create application com.xxxxx.application.MyApplication: java.lang.IllegalStateException: com.xxxxx.di.modules.MyApplicationModule must be set;
I can probably forget to show some classes, so feel free to ask me.
As the error says, you forget to add your ApplicationModule to your component.
By the way, I highly suggest you take a look at AndroidInjector, to avoid creating this Android component hierarchy manually.
public class MyApplication extends Application {
private MyApplicationComponent mMyApplicationComponent;
...
#Override
public void onCreate() {
super.onCreate();
mMyApplicationComponent = DaggerMyApplicationComponent.builder()
.myApplicationModule()
.retrofitModule(new RetrofitModule("https://androidtutorialpoint.com"))
.build();
mMyApplicationComponent.inject(this);

Dagger2 can provide on Presenter layer but null on Model layer

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 don't know why the object injected by dagger2 is null in presenter

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)

Android Using single context for multiple Dagger2 modules

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.

Categories

Resources