In my Android app I'm trying to get a DatabaseHelper object injected into ArticleView but Dagger complains with the following error message:
Error:(11, 7) error: android.content.Context cannot be provided without an #Provides-annotated method.
dk.jener.paperflip.ArticleActivity.database
[injected field of type: dk.jener.paperflip.model.retriever.DatabaseHelper database]
dk.jener.paperflip.model.retriever.DatabaseHelper.<init>(android.content.Context context)
[parameter: android.content.Context context]
How do I fix it?
My code is as follows:
#Singleton
#Module
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
#Inject
public DatabaseHelper(Context context) {
super(context, "foobar", null, 1);
}
...
}
#Module
public class ApplicationContextModule {
private final Context context;
public ApplicationContextModule(Context context) {
this.context = context;
}
#Provides
#Singleton
public Context provideApplicationContext() {
return context;
}
}
#Singleton
#Component(modules = { DatabaseHelper.class })
public interface RetrieverComponent {
void inject(ArticleActivity activity);
}
public class ArticleActivity extends AppCompatActivity {
#Inject
DatabaseHelper database;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
RetrieverComponent component = DaggerRetrieverComponent.builder()
.applicationContextModule(new ApplicationContextModule(getApplicationContext()))
.build();
component.inject(this);
}
...
}
As far as I can figure Context is already provided by ApplicationContextModule#provideApplicationContext .
In the provided code seems that you missed to include the ApplicationContextModule module in your component. It should be like:
#Component(modules = { ApplicationContextModule.class })
Also DatabaseHelper does not need to have #Module annotation (it is not a module but just a normal class).
Related
I'm trying to implement a dagger to my Android Project with MVVM structure. I've succeeded in injecting retrofit API service to my ViewModel using Module and Component with a field injection. But I keep getting an error when I try to inject my RoomDatabase with provision method (DaggerComponent.Builder).
I'm still completely unfamiliar with the dagger, is there something wrong with my code?
DatabaseModule.java
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
#Module
public class DatabaseModule {
private AppDatabase database;
public DatabaseModule(Application application) {
database = Room.databaseBuilder(application, AppDatabase.class,
"pendingDb").build();
}
#Singleton
#Provides
AppDatabase provideDatabase(){
return database;
}
#Singleton
#Provides
PendingTodoListDao providePendingDao(AppDatabase database){
return database.pendingTodoListDao();
}
}
Component
import javax.inject.Singleton;
import dagger.Component;
#Singleton
#Component(modules = {ApiModule.class, ContextModule.class,
DatabaseModule.class})
public interface ApiComponent {
void inject(ApiService service);
void inject(DeliveredViewModel viewModel);
void inject(ToDoViewModel viewModel);
void inject(DeliveredDetailsViewModel viewModel);
void inject(PendingViewModel viewModel);
AppDatabase appDatabase();
}
PendingViewModel.java
public class PendingViewModel extends AndroidViewModel {
#Inject
AppDatabase db;
public PendingViewModel(#NonNull Application application) {
super(application);
DaggerApiComponent.builder().databaseModule(
new DatabaseModule(application)).build();
}
}
As #Sandip Fichadiya correctly suggested, you can use
DaggerApiComponent.create()
only when none of your modules takes constructor parameter.
If even only one of them has parameters, you must use
AppComponent appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
where AppModule is
#Module
public class AppModule {
private final Context context;
public AppModule(Context context) {
this.context = context;
}
#Provides
public Context provideContext() {
return this.context;
}
}
in case, for example, one or more of your modules need a Context
The problem is that Dagger can't see dependency (DataManager) that I want to use in ActivityModule which is defined in ApplicationModule. I have pretty standard Dagger2 project structure:
ActivityComponent.java:
#PerActivity
#Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
void inject(WelcomeFragment fragment); // HERE ERROR IS COMING FROM
}
ApplicationComponent.java:
#Singleton
#Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
Context getContext();
}
ActivityModule.java:
#Module
public class ActivityModule {
private Activity activity;
public ActivityModule(Activity activity) {
this.activity = activity;
}
#Provides
CompositeDisposable provideCompositeDisposable() {
return new CompositeDisposable();
}
#Provides
#PerActivity
WelcomeViewModel provideWelcomeViewModel(DataManager dataManager, CompositeDisposable compositeDisposable) {
return new WelcomeViewModel(dataManager, compositeDisposable);
}
}
ApplicationModule.java:
#Module
public class ApplicationModule {
private final Application application;
public ApplicationModule(Application application) {
this.application = application;
}
#Provides
Context provideContext() {
return application;
}
#Provides
#Singleton
DataManager provideDataManager(DataManagerImpl dataManager) {
return dataManager;
}
#Provides
#Singleton
Retrofit provideRetrofit() {
return new Retrofit.Builder()
.baseUrl(<API_ADDRESS>)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
}
PerActivity.java:
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface PerActivity {
}
The error shows up in WelcomeFragment where I'm trying to inject WelcomeViewModel that needs DataManager and the message is indicating that Dagger can't see where DataManager is Provided:
Error:(12, 10) error: <PACKAGE_NAME>.data.DataManager cannot be provided without an #Provides-annotated method.
<PACKAGE_NAME>.data.DataManager is injected at
<PACKAGE_NAME>.di.module.ActivityModule.provideWelcomeViewModel(dataManager, …)
<PACKAGE_NAME>.views.welcome.WelcomeViewModel is injected at
<PACKAGE_NAME>.views.welcome.WelcomeFragment.viewModel
<PACKAGE_NAME>.views.welcome.WelcomeFragment is injected at
<PACKAGE_NAME>.di.component.ActivityComponent.inject(fragment)
WelcomeViewModel.java:
public class WelcomeViewModel extends ViewModel {
#Inject
public WelcomeViewModel(DataManager dataManager, CompositeDisposable compositeDisposable) {
super(dataManager, compositeDisposable);
}
(...)
}
WelcomeFragment.java:
public class WelcomeFragment extends BaseFragment {
#Inject WelcomeViewModel viewModel;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_welcome, container, false);
getActivityComponent().inject(this);
init();
return view;
}
(...)
}
DataManagerImpl.java:
#Singleton
public class DataManagerImpl implements DataManager {
private Retrofit retrofit;
#Inject
public DataManagerImpl(Retrofit retrofit) {
this.retrofit = retrofit;
}
(...)
}
I can only say that I'm using the same Dagger structure in 2 other projects and always it was working without any problem.
Since you use Component dependencies you need to expose the DataManager in the ApplicationComponent to the ActivityComponent
#Singleton
#Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
Context getContext();
DataManager exposeDataManager();
}
I am trying to inject Context in non-activity class. In my case, I am trying to inject in Model class (from MVP pattern). I need Context to get string from resources. The method result() returns Observable which returns ViewModel which is modified API model. ViewModel is used to show in MainActivity. I tried this code, but Context is null, I think that it is not injected.
public class MainActivityModel implements MainActivityMVP.Model {
#Inject
Context context;
private Repository repository;
public MainActivityModel(Repository repository) {
((App) getApplication()).getComponent().inject(this); //Cannot resolve getApplication() method :(
this.repository = repository;
}
#Override
public Observable<List<ViewModel>> result(String username) {
return repository.getRepositories(username).map(new Func1<List<Repository>, List<ViewModel>>() {
#Override
public List<ViewModel> call(List<Repository> repositories) {
List<ViewModel> viewModelList = new ArrayList<ViewModel>();
for (Repository repository : repositories) {
// here Context is null
viewModelList.add(new ViewModel(context.getString(R.string.repository) + repository.getName()));
}
return viewModelList;
}
});
}
}
This is Component class, where there is inject(MainActivityModel target) method which I am not able to use inject MainActivityModel because getApplication is not available from non-Activity class:
#Singleton
#Component(modules = {ApplicationModule.class, MainActivityModule.class, GithubApiModule.class})
public interface ApplicationComponent {
void inject(MainActivity target);
void inject(MainActivityModel target);
}
I think that Context can be sent via result(String username, Context context) method. But what is the meaning of Dependency Injection if I passing Context as method parameter? Maybe I misunderstood fundamental concept of DI.
So my question: is it possible to inject Context in non-activity class? Or it should be passed as method parameter?
For injecting a Context you will need to write a module with a provides method:
#Module (injects = {MainActivityModel.class})
public class RootModule {
private Context context;
public RootModule(App application) {
this.context = application.getApplicationContext();
}
#Provides
#Singleton
Context provideContext() {
return context;
}
}
In your custom App class:
public class App extends Application {
public ObjectGraph objectGraph;
#Override
public void onCreate() {
super.onCreate();
objectGraph = ObjectGraph.create(getInjectionModule());
}
protected Object getInjectionModule() {
return new RootModule(this);
}
}
And inject in model's constructor:
public MainActivityModel(Repository repository) {
((App) getApplication()).objectGraph.inject(this);
this.repository = repository;
}
Sorry for my english, now i begin learn dagger2 and i cant understand why i have error:
Error:(9, 10) error:
test.dagger.dagger.modules.MainActivityPresenterModule cannot be
provided without an #Inject constructor or from an #Provides- or
#Produces-annotated method.
test.dagger.dagger.modules.MainActivityPresenterModule is injected at
test.dagger.view.activitys.MainActivity.mainActivityPresenterModule
test.dagger.view.activitys.MainActivity is injected at
test.dagger.dagger.components.AppComponent.injectMainActivity(mainActivity)
App
public class App extends Application {
private static AppComponent component;
#Override
public void onCreate() {
super.onCreate();
component = DaggerAppComponent.create();
}
public static AppComponent getComponent() {
return component;
}
}
AppComponent
#Component(modules = {MainActivityPresenterModule.class})
public interface AppComponent {
void injectMainActivity(MainActivity mainActivity);
}
MainActivityPresenterModule
#Module
public class MainActivityPresenterModule {
#Provides
MainActivityPresenter provideActivityPresenter(NetworkUtils networkUtils) {
return new MainActivityPresenter(networkUtils);
}
#Provides
NetworkUtils provideNetworkUtils(){
return new NetworkUtils();
}
}
NetworkUtils
public class NetworkUtils {
public boolean isConnection() {
return true;
}
}
MainActivityPresenter
public class MainActivityPresenter {
NetworkUtils networkUtils;
public MainActivityPresenter(NetworkUtils networkUtils) {
this.networkUtils = networkUtils;
}
public void getUser(){
if(networkUtils.isConnection()) {
Log.e("getUser", "getUser");
} else {
Log.e("no internet", "no internet connection");
}
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
#Inject
MainActivityPresenterModule mainActivityPresenterModule;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
App.getComponent().injectMainActivity(MainActivity.this);
}
}
You can inject only the things that are provided in the classes annotated with #Module (only methods inside that module which are annotated with #Provides). So, you can do #Inject MainActivityPresenter presenter, for instance, not try to inject the whole module, like you tried to do. Modules should be registered on Dagger initialisation, like this (in App#onCreate)
component = DaggerAppComponent.builder()
.mainActivityPresenterModule(MainActivityPresenterModule())
.build()
In MainActivity you only need to call inject to be able to inject your #Inject MainActivityPresenter presenter or any other injects defined in the module, like so:
#Inject MainActivityPresenter presenter
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
(application as App).component.inject(this)
// after #inject(this) above you can start using your injections:
presenter.getUser()
}
Sorry, I wrote code snippets in Kotlin as it was much less to write that way, hopefully you get the idea how it looks in Java.
i'm trying to use Dagger 2 into my apps but i'm having some problems regarding the entities repository and i haven't figured it out what i'm missing.
Here is my Application Component:
#Singleton
#Component(
modules = {
AndroidModule.class,
RepositoryModule.class
}
)
public interface ApplicationComponent {
void inject(AndroidApplication app);
IDependenceyRepository dependencyRepository();
}
My modules:
#Module
public class RepositoryModule {
#Provides #Singleton IDependenceyRepository provideDependendencyRepository(Context context) {
return new DependencyRepository(context);
}
}
#Module
public class AndroidModule {
private final AndroidApplication app;
public AndroidModule(AndroidApplication app) {
this.app = app;
}
#Provides #Singleton Context provideApplicationContext() {
return app;
}
}
My AndroidApplication:
public class AndroidApplication extends Application {
private ApplicationComponent component;
#Override
public void onCreate() {
super.onCreate();
setupGraph();
}
private void setupGraph() {
component = DaggerApplicationComponent.builder()
.androidModule(new AndroidModule(this))
.repositoryModule(new RepositoryModule())
.build();
component.inject(this);
}
public ApplicationComponent component() {
return component;
}
}
And here is my activity:
public class MainActivity extends Activity {
#Inject
IDependenceyRepository repository;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.testRepository();
}
private void testRepository() {
Dependency dep = new Dependency();
dep.setDescription("XXX");
dep.setName("AAAA");
try {
repository.save(dep);
Log.d("", repository.queryAll().get(0).getName());
} catch (SQLException e) {
e.printStackTrace();
}
}
What happens is that i'm getting a null pointer exception into the repository. He is not being injected.
If i add this line though:
repository = ((AndroidApplication) getApplication()).component().dependencyRepository();
It works, but the point of the DI it's not to have to worry about this, or am im wrong about that?
I've tried some other example and tutorials but haven't managed to solve my problem.
Thanks in advance,
david.mihola's comment is correct: in order to be able to have #Inject fields initialized, you need to have a method (typically void inject(MyClass)) in your component.
It's worth noting (not specifically for your question, but it could come up) that if you have a base class:
public class BaseActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((AndroidApplication) getApplication()).component().inject(this);
}
}
and in your component:
void inject(BaseActivity);
Injecting into a subclass that has it's own #Inject fields won't work:
public class ActualActivity extends BaseActivity {
#Inject IDependenceyRepository repo;
}
This is because Dagger needs to know the concrete classes that will be injected at compile time, not runtime (like Dagger 1 could do).