Sorry for the long question / code and my english skills :)
I want to use Dagger-2 in my new android app. I have no experience with Dagger-2 or other dependency libraries. I think i misunderstand something...
Here are the relevant application. If the activity "OverviewActivity" starts it injects the "OverviewPresenter" and so on.. (OverviewActivity->OverviewPresenter->CategoriesPovider->DBOpenHelper->DBDefaults).
At the first start the class DBDefaults should prefill the database with some default values. At the time it should access the resources files i get a NPE. Can anybody tell me why this happens? I tried to use the "Log" tool to check if any needed variable is null but everything looks fine. Gets the DBDefaults class the wrong context?
Finally the method throws a NPE
context.getResources().getStringArray(R.array.category_colors);
But here is my code.
AppComponent.java
#Singleton
#Component(modules = AppModule.class)
public interface AppComponent {
Context getContext();
Application getApplication();
DBOpenHelper getDBOpenHelper();
}
AppModule.java
#Module
public class AppModule {
private final Manager app;
public AppModule(Manager app) {
this.app = app;
}
#Singleton
#Provides
public Application provideApplication() {
return app;
}
#Singleton
#Provides
public Context provideContext() {
return app.getApplicationContext();
}
#Singleton
#Provides
public DBOpenHelper provideDBOpenHelper() {
return new DBOpenHelper(app);
}
}
OverviewComponent.java
#ActivityScope
#Component(dependencies = AppComponent.class)
public interface OverviewComponent {
void inject(OverviewActivity activity);
OverviewPresenter getOverviewPresenter();
}
OverviewPresenter.java
public class OverviewPresenter extends MvpBasePresenter<OverviewView> {
#Inject
public OverviewPresenter(CategoriesProvider provider) {
Log.d("OverviewPresenter", "presenter..");
}
}
CategoriesProvider.java
public class CategoriesProvider extends BaseProvider {
#Inject
public CategoriesProvider(DBOpenHelper dbOpenHelper) {
super(dbOpenHelper);
// Just testing...
dbOpenHelper.getReadableDatabase();
}
}
DBOpenHelper.java
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String db = "manager";
private static final int version = 1;
private Context context;
#Inject
public DBOpenHelper(Context context) {
super(context, db, null, version);
this.context = context;
Log.d("DBOpenHelper", "database...");
if (context == null) {
Log.d("DBOpenHelper", "wtf...");
}
}
#Override
public void onCreate(SQLiteDatabase db) {
// db structure
db.execSQL(CategoriesTable.getCreateTableQuery());
db.execSQL(UsersTable.getCreateTableQuery());
// default values
DBDefaults defaults = new DBDefaults(db, context);
defaults.insertSystemUser();
defaults.insertCategories();
}
}
DBDefaults.java
public class DBDefaults {
private SQLiteDatabase db;
private Context context;
public DBDefaults(SQLiteDatabase db, Context context) {
this.db = db;
this.context = context;
if (context == null) {
Log.d("DBDefaults", "wtf... in Defaults");
}
}
public void insertCategories() {
String[] test = context.getResources().getStringArray(R.array.category_colors);
for (String item : test) {
Log.d("Categories", item);
}
// NPE ?!?!?!
}
}
EDIT
My Manger class. Here I'm storing my app component and init my AppModule
public class Manager extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
My Activity
public class OverviewActivity extends MvpActivity<OverviewView, OverviewPresenter> {
public OverviewComponent overviewComponent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_navigation);
}
#Override
public void inject() {
Log.d("Activity", "inject");
overviewComponent = DaggerOverviewComponent.builder()
.appComponent(((MoneyManager)getApplication()).getAppComponent())
.build();
overviewComponent.inject(this);
}
#Override
public OverviewPresenter getPresenter() {
Log.d("Activity", "getPresenter");
return overviewComponent.getOverviewPresenter();
}
}
If CategoriesProvider is a ContentProvider, you might get null when calling Application.getApplicationContext(). So the context that you're injecting could be null. You can verify this in AppModule. Try logging the result before returning it. Note that Application.getApplicationContext() normally returns the same instance, so you could just return app; instead of what you currently have.
Related
I am trying to inject context in my Interactor class which is giving me a null pointer exception.
I have used the MVP pattern and I am trying to get access to the context in my non-activity class.
I am not really sure if this is the best technique used.
Module:
#Module
public class ContextModule {
private final Context context;
public ContextModule(Context context) {
this.context = context;
}
#Singleton
#Provides
public Context getContext() {
return this.context;
}
}
Component:
#Singleton
#Component(modules = {ContextModule.class})
public interface AppComponent {
void inject(MainActivity mainActivity);
}
App
public class App extends Application {
private AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.contextModule(new ContextModule(this))
.build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
MainActivity
public class MainActivity extends AppCompatActivity implements
TaskContract.IMainView {
#Inject
MainInteractor mainInteractor;
private MainPresnter mainPresnter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((App) getApplication()).getAppComponent().inject(this);
mainPresnter = new MainPresnter(this);
}
#Override
public void getRandomNumber(int rNum) {
Toast.makeText(this, "" + rNum, Toast.LENGTH_SHORT).show();
}
#Override
protected void onResume() {
super.onResume();
mainPresnter.fetchFromService();
}
}
Presenter
public class MainPresnter implements TaskContract.IMainPresenter,
TaskContract.OnTaskCompletionResult {
private TaskContract.IMainView mainView;
private MainInteractor mainInteractor;
public MainPresnter(TaskContract.IMainView mainView) {
this.mainView = mainView;
mainInteractor = new MainInteractor(this);
}
#Override
public void fetchFromService() {
mainInteractor.callService();
}
#Override
public void onSuccess(int rNum) {
mainView.getRandomNumber(rNum);
}
}
Interactor
public class MainInteractor implements TaskContract.IMainInteractor {
private static final int JOB_ID = 100 ;
private Context context;
#Inject
public MainInteractor(Context context) {
this.context = context;
}
public MainInteractor(TaskContract.OnTaskCompletionResult completionListener)
{
TaskService.setCompletionListener(completionListener);
}
#Override
public void callService() {
JobInfo jobInfo = new JobInfo.Builder(JOB_ID,
new ComponentName(context, TaskService.class))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(10000)
.build();
JobScheduler jobScheduler = (JobScheduler)
context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo);
}
}
Gradle
implementation 'com.google.dagger:dagger-android:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
You don't inject the Interactor within your Presenter - therefore it won't have a context.
You could probably restructure your Presenter to require the Interactor as a dependency - this would also mean you'd need to restructure how the completion listener is set.
I am trying to use dagger2 and database in my android application. I can not access the database class with the inject operation. The Database db object I created is returning null. I am new on dagger2. Thanks for any help.
This is my database class.
public class Database extends SQLiteOpenHelper {
#Inject
public Database(#ApplicationContext Context context) {
super(context, Constants.DATABASE_NAME, null, Constants.DATABASE_VERSION);
}
public void createTables(){..}
public void clearAllTables() {...}
public void dropTable(SQLiteDatabase db, String tableName){
db.execSQL("DROP TABLE IF EXISTS " + tableName);
}
#Override
public void onCreate(SQLiteDatabase db) {.. } .. }
This is my module.
#Module abstract public class DatabaseModule {
private Application application;
public DatabaseModule(Application application) {
this.application = application;
}
#Provides
#ApplicationContext
Context provideContext() {
return application;
}}
And component
#ActivityScope #Subcomponent public interface DatabaseSubcomponent {
#ApplicationContext
Context getContext();
Database getDb(); }
Db object returns null as follows :
#Inject
Database db;
I will be grateful if you tell me where I made a mistake. Thanks in advance.
define a scope annotator for yourself #DatabaseScope.java
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface DatabaseScope {
}
And then the DatabaseModule.java should provide context and database objects which is your module(during injection of database object dagger automatically injects context for you from the object graph)
#Module
public class DatabaseModule {
private Application application;
public DatabaseModule(Application application){
this.application = application;
}
#Provides
#DatabaseScope
Context provideContext(){
return application;
}
#Provides
#DatabaseScope
Database provideDatabase(Context context){
return new Database(context);
}
}
And then the DatabaseComponent.java . You need not annotate this as a subcomponent
#DatabaseScope
#Component(modules = {DatabaseModule.class})
public interface DatabaseComponent {
void inject(MainActivity mainActivity);
}
and then your database.java
public class Database extends Object {
public Database(Context context) {
Log.e("database creted"," ");
}
String getmessage(){
return "jenison";
}
}
and finally inject this in your activity
public class MainActivity extends AppCompatActivity {
#Inject
Database db;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DatabaseComponent component=DaggerDatabaseComponent.builder().databaseModule(new DatabaseModule(getApplication())).build();
component.inject(this);
Log.e("test"," "+db.getmessage());
}
}
I checked all questions but did not find any clue. I stripped my problem to a simplest code:
Situation:
I want to have:
CatComponent catComponent = DaggerCatComponent.builder()
.kameModule(new KameModule(MainActivity.this))
.build();
catComponent.getCatAnalyzer().analyze();
I have created component:
#Component(modules = {KameModule.class, CatAnalyzerModule.class})
public interface CatComponent {
CatAnalyzer getCatAnalyzer();
}
And modules:
#Module
public class KameModule {
private Context context;
public KameModule(Context context) {
this.context = context;
}
#Provides
KameCat provideKameCat() {
return new KameCat(context );
}
}
#Module(includes = KameModule.class)
public class CatAnalyzerModule {
#Inject
KameCat cat;
#Provides
CatAnalyzer provideCatAnalyzer() {
return new CatAnalyzer(cat);
}
}
And classes:
public class KameCat {
Context context;
public KameCat(Context context) {
this.context = context;
}
public void doCatStuff() {
Toast.makeText(context, "Poo and Meow", Toast.LENGTH_LONG).show();
}
}
public class CatAnalyzer {
#Inject
KameCat cat;
#Inject
public CatAnalyzer(KameCat cat) {
this.cat = cat;
}
void analyze() {
cat.doCatStuff();
}
}
When I retrieve my CatAnalyzer object from CatComponent it has cat field nulled.
I have no idea why Dagger won't inject it. Could you guide me somehow?
Proper code:
#Module(includes = KameModule.class)
public class CatAnalyzerModule {
#Inject //remove this
KameCat cat;// remove this
#Provides
// Add cat as a argument and let KameModule provide it..
CatAnalyzer provideCatAnalyzer(KameCat cat) {
return new CatAnalyzer(cat);
}
}
Thanks to:
https://www.future-processing.pl/blog/dependency-injection-with-dagger-2/
Here's my code, which I based on some old tutorial found on the internet. There really should be some examples on the main site of Dagger 2, I found it really difficult to understand how to implement all this.
It's really a lot of work to get such a simple app to run. I have two questions:
Do I have to call DaggerLoggerComponent in every class I want to get some components like my Logger class?
Also how can I make the scope of the Logger class a singleton? Right now every button click creates a new logger instance.
Probably I dont understand some underlying concepts, I've only used dependency injection in Spring before and all of this seems strange to me.
public class MainActivity extends AppCompatActivity {
private Button button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init(){
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();
component.getLogger().log("Hello!",MainActivity.this);
}
});
}
}
public class Logger {
private static int i = 0;
public Logger(){
i++;
}
public static int getI() {
return i;
}
public void log(String text, Context context){
Toast.makeText(context,text+" "+i,Toast.LENGTH_SHORT).show();
}
}
#Singleton
#Component(modules={LoggerModule.class})
public interface LoggerComponent {
Logger getLogger();
}
#Module
public class LoggerModule {
#Provides
#Singleton
Logger provideLogger(){
return new Logger();
}
}
The answer is
public class MainActivity extends AppCompatActivity {
#OnClick(R.id.button) //ButterKnife
public void onClickButton() {
logger.log("Hello!");
}
#Inject
Logger logger;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Injector.INSTANCE.getApplicationComponent().inject(this);
ButterKnife.bind(this);
}
#Override
protected void onDestroy() {
ButterKnife.unbind(this);
super.onDestroy();
}
}
public class Logger {
private static int i = 0;
private CustomApplication customApplication;
public Logger(CustomApplication application) {
this.customApplication = application;
i++;
}
public static int getI() {
return i;
}
public void log(String text){
Toast.makeText(customApplication, text + " " + i,Toast.LENGTH_SHORT).show();
}
}
public interface LoggerComponent {
Logger logger();
}
#Module
public class ApplicationModule {
private CustomApplication customApplication;
public ApplicationModule(CustomApplication customApplication) {
this.customApplication = customApplication;
}
#Provides
public CustomApplication customApplication() {
return customApplication;
}
}
#Module
public class LoggerModule {
#Provides
#Singleton
Logger provideLogger(){
return new Logger();
}
}
#Singleton
#Component(modules={LoggerModule.class, ApplicationModule.class})
public interface ApplicationComponent extends LoggerComponent {
CustomApplication customApplication();
void inject(MainActivity mainActivity);
}
public class CustomApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Injector.INSTANCE.initializeApplicationComponent(this);
}
}
public enum Injector {
INSTANCE;
private ApplicationComponent applicationComponent;
public ApplicationComponent getApplicationComponent() {
return applicationComponent;
}
void initializeApplicationComponent(CustomApplication customApplication) {
this.applicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(customApplication))
.build();
}
}
This is currently our Dagger2 architecture.
EDIT: This is from our actual code for Retrofit stuff from our application we're making:
public interface RecordingService {
ScheduledRecordsXML getScheduledRecords(long userId)
throws ServerErrorException;
}
public class RecordingServiceImpl
implements RecordingService {
private static final String TAG = RecordingServiceImpl.class.getSimpleName();
private RetrofitRecordingService retrofitRecordingService;
public RecordingServiceImpl(RetrofitRecordingService retrofitRecordingService) {
this.retrofitRecordingService = retrofitRecordingService;
}
#Override
public ScheduledRecordsXML getScheduledRecords(long userId)
throws ServerErrorException {
try {
return retrofitRecordingService.getScheduledPrograms(String.valueOf(userId));
} catch(RetrofitError retrofitError) {
Log.e(TAG, "Error occurred in downloading XML file.", retrofitError);
throw new ServerErrorException(retrofitError);
}
}
}
#Module
public class NetworkClientModule {
#Provides
#Singleton
public OkHttpClient okHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.interceptors().add(new HeaderInterceptor());
return okHttpClient;
}
}
#Module(includes = {NetworkClientModule.class})
public class ServiceModule {
#Provides
#Singleton
public RecordingService recordingService(OkHttpClient okHttpClient, Persister persister, AppConfig appConfig) {
return new RecordingServiceImpl(
new RestAdapter.Builder().setEndpoint(appConfig.getServerEndpoint())
.setConverter(new SimpleXMLConverter(persister))
.setClient(new OkClient(okHttpClient))
.setLogLevel(RestAdapter.LogLevel.NONE)
.build()
.create(RetrofitRecordingService.class));
}
//...
}
public interface RetrofitRecordingService {
#GET("/getScheduledPrograms")
ScheduledRecordsXML getScheduledPrograms(#Query("UserID") String userId);
}
public interface ServiceComponent {
RecordingService RecordingService();
//...
}
public interface AppDomainComponent
extends InteractorComponent, ServiceComponent, ManagerComponent, ParserComponent {
}
#Singleton
#Component(modules = {
//...
InteractorModule.class,
ManagerModule.class,
ServiceModule.class,
ParserModule.class
//...
})
public interface ApplicationComponent
extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {
void inject(DashboardActivity dashboardActivity);
//...
}
Do I have to call DaggerLoggerComponent in every class I want to get some components like my Logger class?
Yes for all classes that created by the system like Application, Activity and Service. but for you own classes, you don't need that. just annotate you constructor with #inject and dagger will provide your dependencies.
Also how can I make the scope of the Logger class a singleton? Right
now every button click creates a new logger instance.
Your setup for singleton is correct. but you have to initialize the component one time after the activity is created (onCreate) in order to let dagger to inject all fields. Also you can utilize lazy injection feature if you don't need the Logger object right away.
#Inject
Logger logger;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();
component.inject(this);
init();
}
Then you can access your object without take the reference from the component:
private void init(){
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
logger.log("Hello!",MainActivity.this);
}
});
}
In summary:
You have to initialize the component in all classes that use field injections.
UPDATE:
To do the actual injection, you have to declare inject() method into your component and dagger will automatically implement it. This method will take care of provide any object annotated with #Inject.
Let's say I have three Dagger modules:
#Module()
public class MainModule {
private Application application;
public MainModule(Application application) {
this.application = application;
}
#Provides
#Singleton
Application provideApplication() {
return application;
}
#Provides
Something provideSomething(Application application) {
return new Something(application);
}
}
#Module()
public class SubModule1 {
private Activity activity;
public SubModule1(Activity activity) {
this.activity = activity;
}
#Provides
#Singleton
Activity provideActivity() {
return activity;
}
#Provides
SomethingElse provideSomethingElse(Activity activity) {
return new SomethingElse(activity);
}
}
#Module()
public class SubModule2 {
private Activity activity;
public SubModule2(Activity activity) {
this.activity = activity;
}
#Provides
#Singleton
Activity provideActivity() {
return activity;
}
#Provides
Anything provideAnything(Activity activity) {
return new Anything(activity);
}
}
Now assuming I want to do something like this:
public class MyApplication extends Application {
...
#Override
public void onCreate() {
super.onCreate();
objectGraph = ObjectGraph.create(new MainModule(this));
objectGraph.inject(this);
}
}
public class MyActivity1 extends Activity {
...
#Override
public void onCreate() {
super.onCreate();
objectGraph = ((MyApplication) getApplication()).getObjectGraph().plus(new SubModule1(this)
objectGraph.inject(this);
}
}
public class MyActivity2 extends Activity {
...
#Override
public void onCreate() {
super.onCreate();
objectGraph = ((MyApplication) getApplication()).getObjectGraph().plus(new SubModule2(this)
objectGraph.inject(this);
}
}
It also might happen that I have such a class:
public class TestClass {
#Inject
SomethingElse somethingElse;
#Inject
Anything anything;
}
What is the right way to implement this? How should I use includes, addsTo, injects, library, complete and plus()?
I usually set Dagger up as follows:
Extend Application like this:
public class InjectingApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
objectGraph = ObjectGraph.create(new MainModule(this));
objectGraph = ObjectGraph.create(new SubModule1(this));
objectGraph = ObjectGraph.create(new ActivityModule());
objectGraph.inject(this);
}
}
Then have a module for adding references to your Activities (called ActivityModule.java in this instance):
#Module(
injects = {
MyActivity1.class,
MyActivity2.class }
) public class ActivityModule { }
Then your Activities will look like this:
public class MyActivity1 extends Activity {
#Inject
Something something;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((InjectingApplication) getApplication()).inject(this);
}
}
Finally, your modules will look like this:
#Module()
public class MainModule {
private Context context;
public MainModule(Context context) {
this.context = context;
}
#Provides
Something provideSomething(Context context) {
return new Something(context);
}
}
#Module()
public class SubModule1 {
private Context context;
public SubModule1(Context context) {
this.context = context;
}
#Provides
SomethingElse provideSomethingElse(Context context) {
return new SomethingElse(context);
}
}