Module depending on another module in Dagger - java

I'm trying to use Dagger to do Dependency Injection on an app that I'm building, and running into trouble constructing proper DAGs when I have one package's Module depending on values provided by the Injector (presumably provided by another Module).
If I have a simple module for some configurable variables (that I might want to swap out for testing environments, for example)
#Module(
injects = DependentModule.class,
)
public class ConfigModule {
#Provides #Named("ConfigOption") String provideConfigOption() {
return "This Module's configurable option!";
}
}
and another module depends on it, e.g.
#Module(
injects = {
TopLevelClass.class
}
)
public class DependentModule {
#Inject #Named("ConfigOption") String configOption;
public DependentModule() {
ObjectGraph.create(this).inject(this);
doSomethingWithConfig(configOption);
}
#Provides #Singleton UsefulValue provideUsefulValue() {
// Whatever this module needs to do...
}
}
The line where I try to bootstrap the injection in the constructor fails, and it complains that I haven't specified an explicit injects line in a proper module.
Through trial-and-error I see this goes away if in #Module I add a line include = ConfigModule.class, but this strikes me as semantically wrong, since a) the DAG I'll be creating will now include the values of both modules, rather than just one, and b) it defeats the purpose/flexibility of DI in the first place to link a specific Module rather than simply let Dagger inject the appropriate value.
I'm presuming I shouldn't be creating an Object Graph with this only to inject into it? But then I run into the issue of not linking a specific Module...
Succinctly:
What is the 'proper' way to Inject values into one Modules that may be provided from other Modules? Here I'm using field injection, but my experiments with constructor injection have also resulted in a lot of failure.
Relatedly, when is it appropriate to use addsTo vs. includes?
Thanks :)

You don't need to do any of injection (field or constructor) in one module from another explicitly. Just use addsTo and includes.
includes allows to add modules to another and use everything they provide. Example:
#Module()
public class ModuleA {
#Provides #Named("ValueA") String provideValueA() {
return "This is ValueA";
}
}
#Module(
includes = ModuleA.class
)
public class ModuleB {
// ValueA comes from ModuleA
#Provides #Named("ValueB") String provideValueB(#Named("ValueA") String valueA) {
return valueA + " and ValueB";
}
}
addsTo is used with ObjectGraph.plus(Object... modules). When graph is already created and contains some modules (e.g. in Application class), you can create new graph (e.g. in Activity) using plus. Example:
#Module()
public class ApplicationModule {
#Provides #Named("ValueA") String provideValueA() {
return "This is ValueA";
}
}
#Module(
addsTo = ApplicationModule.class
)
public class ActivityModule {
// ValueA comes from ApplicationModule
#Provides #Named("ValueB") String provideValueB(#Named("ValueA") String valueA) {
return valueA + " and ValueB";
}
}
public class DemoApplication extends Application {
private ObjectGraph graph;
#Override public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(getModules().toArray());
}
protected List<Object> getModules() {
return Arrays.asList(
new ApplicationModule()
);
}
public void inject(Object object) {
graph.inject(object);
}
public ObjectGraph getObjectGraph() {
return graph;
}
}
public class DemoActivity extends Activity {
private ObjectGraph activityGraph;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create the activity graph by .plus-ing our modules onto the application graph.
DemoApplication application = (DemoApplication) getApplication();
activityGraph = application.getApplicationGraph().plus(new ActivityModule());
// Inject ourselves so subclasses will have dependencies fulfilled when this method returns.
activityGraph.inject(this);
}
#Override protected void onDestroy() {
// Eagerly clear the reference to the activity graph to allow it to be garbage collected as
// soon as possible.
activityGraph = null;
super.onDestroy();
}
}
Also you can check this example to create scopes of graphs.

Related

Dagger 2 Android - inject() dependencies into ViewModels vs Application with dependency reference(s)

I am creating a basic Android application with Dagger 2. I was having a lot of difficulty understanding how to use it properly until I came across this great talk by Jake Wharton. In it, he demonstrates using Dagger 2 with a "Tweeter" app. At ~22:44, he shows that an app's #Inject fields can be satisfied with an inject method. He later shows a simple Android implementation of this.
My app's ViewModels rely on a repository class. I'm using Dagger 2 to inject this repository into the ViewModels, through the Application class, like this:
//In my Dagger 2 component
#Singleton
#Component(module = {MyRepositoryModule.class})
public interface MyRepositoryComponent{
void inject(MyViewModel viewModel);
}
//In MyApplication
public class MyApplication extends Application{
private MyRepositoryComponent repoComponent;
//Instantiate the component in onCreate...
public MyRepositoryComponent getMyRepositoryComponent(){
return repoComponent;
}
}
//Finally, in my ViewModel
public MyViewModel extends AndroidViewModel{
#Inject
public MyRepository repo;
public MyViewModel(#NonNull MyApplication app){
repo = app.getMyRepositoryComponent().inject(this);
}
}
I went with this approach because I can override the MyApplication class and use fake components for testing (which is one of my main goals here). Previously, the only way I was able to inject dependencies was by building my component inside the ViewModels, which makes it impossible to substitute with fakes.
For a simple app like this, I know I could just do away with the inject method and hold a reference to the repository in the MyApplication class. However, assuming there are more dependencies to worry about, would this be a common/good/testing-friendly approach to injecting dependencies for Activities and ViewModels in Android?
After inspiration from EpicPandaForce's answer and some research (see this article), I've found a solution I'm happy with.
I decided to drop Dagger 2 from my project because I was over-engineering it. My app relies on a repository class and now a ViewModelProvider.Factory implementation, which are both needed as soon as the app runs. I learned enough about Dagger for my own satisfaction, so I feel comfortable leaving it out of this particular project and creating the two dependencies in an Application class. These classes look like this:
My Application class, which creates my ViewModel Factory, gives it it's repository, and exposes a getViewModelFactory() method to my Activities:
public class JourneyStoreApplication extends Application {
private final JourneyStoreViewModelFactory journeyStoreViewModelFactory;
{
// Instantiate my viewmodel factory with my repo here
final JourneyRepository journeyRepository = new JourneyRepositoryImpl();
journeyStoreViewModelFactory = new JourneyStoreViewModelFactory(journeyRepository);
}
#Override
public void onCreate() {
super.onCreate();
}
public JourneyStoreViewModelFactory getViewModelFactory(){
return journeyStoreViewModelFactory;
}
}
My ViewModel Factory, which creates new ViewModels with a repository reference. I'll be expanding this as I add more Activity classes and ViewModels:
public class JourneyStoreViewModelFactory implements ViewModelProvider.Factory {
private final JourneyRepository journeyRepository;
JourneyStoreViewModelFactory(JourneyRepository journeyRepository){
this.journeyRepository = journeyRepository;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
if(modelClass == AddJourneyViewModel.class){
// Instantiates the ViewModels with their repository reference.
return (T) new AddJourneyViewModelImpl(journeyRepository);
}
throw new IllegalArgumentException(String.format("Requested class %s did not match expected class %s.", modelClass, AddJourneyViewModel.class));
}
}
My AddJourneyActivity class, which uses the AddJourneyViewModel:
public class AddJourneyActivity extends AppCompatActivity {
private static final String TAG = AddJourneyActivity.class.getSimpleName();
private AddJourneyViewModel addJourneyViewModel;
private EditText departureTextField;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_journey);
JourneyStoreApplication app = (JourneyStoreApplication) getApplication();
addJourneyViewModel = ViewModelProviders
// Gets the ViewModelFactory instance and creates the ViewModel.
.of(this, app.getViewModelFactory())
.get(AddJourneyViewModel.class);
departureTextField = findViewById(R.id.addjourney_departure_addr_txt);
}
//...
}
But this still leaves the question of testing, which was one of my main issues.
Side note: I made all of my ViewModel classes abstract (with just methods) and then I implemented them for my real app and the test code. This is because I find it easier than extending my ViewModels directly, then trying to override their methods and shadow their state to create a fake version.
Anyway, I extended my JourneyStoreApplication class (contradicting myself I know, but it's a small class so it's easy to manage) and used that to create a place to provide my fake ViewModels:
public class FakeJourneyStoreApplication extends JourneyStoreApplication {
private final JourneyStoreViewModelFactory fakeJourneyStoreViewModelFactory;
{ // Create my fake instances here for my tests
final JourneyRepository fakeJourneyRepository = new FakeJourneyRepositoryImpl();
fakeJourneyStoreViewModelFactory = new FakeJourneyStoreViewModelFactory(fakeJourneyRepository);
}
#Override
public void onCreate() {
super.onCreate();
}
public JourneyStoreViewModelFactory getViewModelFactory(){
return fakeJourneyStoreViewModelFactory;
}
}
I made fake implementations of my ViewModels and returned instances of them from FakeJourneyStoreViewModelFactory. I might simplify this later as there's probably more "fake" boilerplate than there needs to be.
Going off this guide (section 4.9), I extended AndroidJUnitRunner to provide my fake Application to my tests:
public class CustomTestRunner extends AndroidJUnitRunner {
#Override
public Application newApplication(ClassLoader cl, String className, Context context)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return super.newApplication(cl, FakeJourneyStoreApplication.class.getName(), context);
}
}
And finally, I added the custom test runner to my build.gradle file:
android {
defaultConfig {
// Espresso
testInstrumentationRunner "com.<my_package>.journeystore.CustomTestRunner"
}
}
I'm going to leave this question open for another 24 hours in case anyone has useful things to add, then I'll choose this as the answer.

Dagger 2 unable to do constructor injection

I have an app with activities and fragments with dependencies injected via dagger 2
I am able to do field injection in activities and fragments but not able to do constructor injection in other classes.
Here's my relevant code
#Module
public abstract class MainFragmentProvider {
#ContributesAndroidInjector(modules = HomeFragmentModule.class)
abstract HomeFragment provideHomeFragmentFactory();
}
and
#Module
public class HomeFragmentModule {
...
#Provides
static HomePresenter provideHomePresenter(HomeView homeView, HomeInteractor homeInteractor) {
return new HomePresenter(homeView, homeInteractor);
}
How can I write code so that I can get dependencies directly in HomePresenter by constructor injection instead of writing provideMethods in module. I am doing this because every time I want to change the constructor arguments in this case, I need to change the module code as well.
How can I do something like this in HomePresenter's constructor?
#Inject
public HomePresenter(HomeView homeView, HomeInteractor homeInteractor) {
this.homeInteractor = homeInteractor;
this.homeView = homeView;
}
To inject constructor, Dagger has to know where to get parameters passed to it i.e.
you have to provide
HomeView homeView, HomeInteractor homeInteractor
So create also methods for providing other dependencies:
#Provides
static HomeView provideHomeView() {
return ...
}
#Provides
static HomeInteractor provideHomeInteractor() {
return ...
}
I don't know much about the android extensions for dagger 2 but as far as I know there are two ways to achieve the result you are looking for.
In the relevant component you can specify a method with your type:
interface SomeComponent {
HomePresenter presenter(); // Method name does not matter here, only the type
}
and access it like this
class Home {
HomePresenter presenter;
void initialize() { //This could be your onCreate or wherever you typically inject
presenter = getSomeComponent().presenter();
}
}
or you can request it if you specify an inject method for your Home object:
interface SomeComponent {
void inject(Home home);
}
class Home {
#Inject HomePresenter presenter;
void initialize(){
getSomeComponent().inject(this);
}
}
In both cases you must ensure your Component includes the appropriate Modules.

Dagger 2 Object cannot be provided without an #Inject constructor

I'm fairly new to Dagger 2 and I have the following classes.
I have 2 modules:
DaoSessionModule
#Module
public class DaoSessionModule {
private DaoSession daoSession;
private Context context;
public DaoSessionModule(Context context) {
this.context = context;
if(daoSession == null) {
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this.context, "my_pocket");
Database db = helper.getWritableDb();
daoSession = new DaoMaster(db).newSession();
}
}
#Provides
LanguageDao providesLanguageDao() {
return daoSession.getLanguageDao();
}
#Provides
CategoryDao providesCategoryDao() {
return daoSession.getCategoryDao();
}
}
and GlobalPrefModule
#Module
public class GlobalPrefModule {
private GlobalPref globalPerf;
public GlobalPrefModule(GlobalPref globalPerf) {
this.globalPerf = globalPerf;
}
#Provides
public GlobalPref providesGlobalPref() {
return this.globalPerf;
}
}
and their components go as:
#Singleton
#Component(modules = {DaoSessionModule.class})
public interface DaoSessionComponent {
void inject(SplashActivity activity);
}
and
#Singleton
#Component(modules = {GlobalPrefModule.class })
public interface GlobalPrefComponent {
void inject(SplashActivity activity);
}
and I build both in my application class:
daoSessionComponent = DaggerDaoSessionComponent.builder()
.daoSessionModule(new DaoSessionModule(this))
.build();
globalPrefComponent = DaggerGlobalPrefComponent.builder()
.globalPrefModule(new GlobalPrefModule(new GlobalPref()))
.build();
and inject them in my splash activity:
public class SplashActivity extends BaseActivity {
#Inject
LanguageDao languageDao;
#Inject
GlobalPref globalPerf;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initInjections();
}
private void initInjections() {
ZoopiApplication.app().getDaoSessionComponent().injectDao(this);
ZoopiApplication.app().getGlobalPrefComponent().injectGlobalPref(this);
}
}
now the problem I'm facing is that if I only inject DaoSession in my splash and comment out the GlobalPref impl it'll simply work but the moment I add GlobalPref along side with Daosession it fails to build and gives me the following error messages:
Error:(8, 52) error: cannot find symbol class DaggerDaoSessionComponent
Error:(9, 52) error: cannot find symbol class DaggerGlobalPrefComponent
Error:(16, 10) error: mypocket.com.zoopi.GlobalPref cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
mypocket.com.zoopi.GlobalPref is injected at
mypocket.com.zoopi.activities.SplashActivity.globalPerf
mypocket.com.zoopi.activities.SplashActivity is injected at
mypocket.com.zoopi.dagger.dagger2.components.DaoSessionComponent.injectDao(activity)
Error:(16, 10) error: mypocket.com.zoopi.models.LanguageDao cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
mypocket.com.zoopi.models.LanguageDao is injected at
mypocket.com.zoopi.activities.SplashActivity.languageDao
mypocket.com.zoopi.activities.SplashActivity is injected at
mypocket.com.zoopi.dagger.dagger2.components.GlobalPrefComponent.injectGlobalPref(activity)
and both generated classes DaggerDaoSessionComponent and DaggerGlobalPrefComponent are generated in the build foloder.
What could be the reason that I can't inject both objects into the same activity?
Injection has to be done from one component, and one component only.
It should be easy to see that the error message states that the object that can't be provided is the one you try to inject by the other component.
Dagger does not do "half" injections and one component has to inject all the fields. If partial injection would be possible you could end up with inconsistent states, since Dagger has no way of knowing how, when, or where you'd inject the rest of the fields. In short, it's just not possible. You'll have to use a single component.
but then I'll have many more modules soon and I don't know if it's a good idea to have one component to handle all modules...
That's okay. You will end up with quite a bunch of modules and possible quite a few components, depending on your setup. Make sure to use SubComponents where appropriate and you can even have modules include other modules, if you have big dependency groups split over multiple modules.

How to inject dependencies if your module itself take parameters in as a constructor?

I have just learned about Dependency injection (DI) and I am beginning to like it. To inject dependencies I am using Google Guice framework. Everything was running conceptually fine but while writing a module a thought came to my mind that what if my module require dependencies as a constructor, after all, it is just a class extending AbstractModule.
So, basically, I have 3 modules as a whole.
Environment Module
public class EnvModule extends AbstractModule {
#Override
protected void configure() {
install(new Servicemodule());
}
}
ServiceModule
public class ServiceModule extends AbstractModule {
private final boolean isEnabled;
#Override
protected void configure() {
if (isEnabled) {
install (new ThirdModule());
}
}
ThirdModule (It does not take any arguments in any constructor and have some bindings of its own)
Basically, the variable in the service module defines whether my application needs to install the third module or not. And that variable is defined in an application configuration file. So how do I inject that variable in the ServiceModule? As the field is final, setter injection is not possible, is there a way to use construction injection or field injection to inject the value.
I see the following options:
Use system variable:
ServiceModule() {isEnabled = System.getProperty("isThirdModuleEnabled")};
Read the config file directly in the ServiceModule() constructor
Use #Provides:
class ServiceModule ... {
#Provide #Singleton ThirdModuleParam getThirdModuleParam(...) {
//read the config file
ThirdModuleParam res = new ThirdModuleParam();
res.setIsEnabed(...);
return res;
}
}
class ThirdModule {
#Provide SomeThirdModuleClass getIt(ThirdModuleParam param) {
return param.isEnabled() ? new SomeThirdModuleClass() : null;
}

How do I re-inject specific object with Dagger

In my app I have two modes: demo and real. In demo mode I mock server communication by using another class:
#Module(...)
public class CommunicationModule {
#Provides #Singleton CommunicationWrapper provideNetworkBusWrapper(Application app) {
boolean isDebug = ((MyApplication) app).isDebug();
CommunicationWrapper result = null;
if (isDebug) {
result = new DemoWrapper(app);
} else {
result = new NetworkWrapper(app);
}
return result;
}
}
Now after a specific user action I'd like to reinitialize this dependency. Is it it possible to do it without rebuilding the whole ObjectGraph?
You can re-inject the object so the implementation will be changed based on your flag. Just remove the #Singleton annotation from your provide method.
Here is a sample code:
public class SomeActivity extends Activity {
#Inject CommunicationWrapper mCommunicationWrapper;
#Override
protected void onCreate(Bundle savedInstanceState) {
((MyApplication) getApplication()).inject(this);
// using NetworkWrapper
...
}
public void userActionHandler(){
((MyApplication) getApplication()).setDebug(true);
((MyApplication) getApplication()).inject(this);
// using DemoWrapper
...
}
Remember that all your dependencies of the class will be re-injected.
To have 2 versions that you include conditionally, but want to swap out on some other changing condition, you could provide the 2 types from singletons, and then provide to your dependency from the without the singleton.
// DemoWrapper.java
#Singleton // Alternatively, you could add provides methods for the wrapper types and annotate those with #Singleton
class DemoWrapper { ... }
// NetworkWrapper.java
#Singleton
class NetworkWrapper { ... }
// CommunicationModule.java
#Module(
...,
injects = {
DemoWrapper.class,
NetworkWrapper.class
}
)
public class CommunicationModule {
#Provides
CommunicationWrapper provideNetworkBusWrapper(Application app, DemoWrapper demo, NetworkWrapper network) {
boolean isDebug = ((MyApplication) app).isDebug();
return isDebug ? demo : network;
}
}
This comment treats about Dagger 1
I am not realy sure if it will help you but have you analysed U2020 app created by Jake Wharton as example of using Dagger?
Based on my analyse he adds two graphs in debug mode (standard module and debug module). Debug module can override functionalities of standard module (on runtime), but I haven't found where exactly it implemented. In this app it's provided by button in menu - maybe you will find it faster (I have some problems with gradle)

Categories

Resources