Map enums to Dagger injected classes (MapBinder equivalent) - android

I'm trying to inject a class at runtime using a factory to map enums to class types, But I'm running into an issue where it will throw an error when I try to inject it.
java.lang.IllegalArgumentException: No inject registered for members/com.example.android.push.DefaultPushHandlerStrategy. You must explicitly add it to the 'injects' option in one of your modules.
public class PushFactory {
private Map<PushType, Class<? extends PushHandlerStrategy>> classMap = new HashMap<PushType, Class<? extends PushHandlerStrategy>>();
#Inject
public PushFactory() {
classMap.put(PushType.DEFAULT, DefaultPushHandlerStrategy.class);
classMap.put(PushType.VIDEOS, VideosPushHandlerStrategy.class);
classMap.put(PushType.MESSAGE, MessagePushHandlerStrategy.class);
}
public PushHandlerStrategy getPushHandlerStategy(PushType type){
Class<? extends PushHandlerStrategy> klazz = classMap.get(type);
if(klazz == null){
klazz = DefaultPushHandlerStrategy.class;
}
ObjectGraph graph = App.getApplication().getObjectGraph();
return graph.get(klazz); // this line throws the exception
}
}
Basically, what I'm trying to achieve is instantiating a strategy based on some data that comes in a GCM push.
I DO have the following registered in a module.
#Module(
injects = {
PushFactory.class,
PushBroadcastReceiver.class
},
complete = false,
library = false
)
public class PushModule {
}
Any ideas what's wrong with my approach?
Edit:
I was able to achieve what I wanted by injecting providers, but it seems a bit cumbersome. Any way around this?
public class PushFactory {
private Map<PushType, Provider<? extends PushHandlerStrategy>> providerMap = new HashMap<PushType, Provider<? extends PushHandlerStrategy>>();
#Inject
public PushFactory(Provider<DefaultPushHandlerStrategy> d, Provider<VideosPushHandlerStrategy> v, Provider<MessagePushHandlerStrategy> m) {
providerMap.put(PushType.DEFAULT, d);
providerMap.put(PushType.VIDEOS, v);
providerMap.put(PushType.MESSAGE, m);
}
public PushHandlerStrategy getPushHandlerStrategy(PushType type){
Provider<? extends PushHandlerStrategy> provider = providerMap.get(type);
if(provider == null){
provider = providerMap.get(PushType.DEFAULT);
}
return provider.get();
}
}

Your original solution should be achievable but it seems like you're probably missing the injects definition of those class in your PushModule. Since you're creating those objects using objectGraph.get(class) directly and not through field or constructor injection, without adding those classes to the injects Dagger cannot know that those classes are needed and will not create any plumbing for them and therefore will fail at runtime.
#Module(
injects = {
PushFactory.class,
PushBroadcastReceiver.class,
DefaultPushHandlerStrategy.class,
VideosPushHandlerStrategy.class,
MessagePushHandlerStrategy.class
},
complete = false,
library = false
)
public class PushModule {
}

Related

android retrofit2, dagger2 unit test

I learn how to test the presenter layer of MVP architecture in android, my presenter using retrofit 2 and in my activity I used dagger 2 as dependency injection to my presenter, this is my Dagger and presenter injection looks like:
#Inject
AddScreenPresenter addScreenPresenter;
This is the Dagger builder :
DaggerAddScreenComponent.builder()
.netComponent(((App) getApplicationContext()).getNetComponent())
.addScreenModule(new AddScreenModule(this, new ContactDatabaseHelper(this)))
.build().inject(this);
and this is my presenter constructor :
#Inject
public AddScreenPresenter(Retrofit retrofit, AddScreenContact.View view, ContactDatabaseHelper contactDatabaseHelper)
{
this.retrofit = retrofit;
this.view = view;
this.contactDatabaseHelper = contactDatabaseHelper;
}
I have write the unit test class and mock the Retrofit class, but when I run it, the error appears :
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
This is the test class :
#RunWith(MockitoJUnitRunner.class)
public class AddScreenPresenterTest {
private AddScreenPresenter mAddPresenter;
#Mock
private Retrofit mRetrofit;
#Mock
private Context mContext;
#Mock
private AddScreenContact.View mView;
#Mock
private ContactDatabaseHelper mContactDatabaseHelper;
String firstName, phoneNumber;
Upload upload;
#Before
public void setup() {
mAddPresenter = new AddScreenPresenter(mRetrofit, mView, mContactDatabaseHelper);
firstName = "aFirstName";
phoneNumber = "998012341234";
Uri path = Uri.parse("android.resource://"+BuildConfig.APPLICATION_ID+"/" + R.drawable.missing);
upload = new Upload();
upload.title = firstName;
upload.description = "aDescription";
upload.albumId = "XXXXX";
upload.image = new File(path.getPath());
}
#Test
public void checkValidationTest() {
verify(mAddPresenter).checkValidation(firstName, phoneNumber);
}
#Test
public void uploadMultiPartTest() {
verify(mAddPresenter).uploadMultiPart(upload);
}
}
this is my module :
#Module
public class AddScreenModule {
private final AddScreenContact.View mView;
private final ContactDatabaseHelper mContactDatabaseHelper;
public AddScreenModule (AddScreenContact.View view, ContactDatabaseHelper contactDatabaseHelper)
{
this.mView = view;
this.mContactDatabaseHelper = contactDatabaseHelper;
}
#Provides
#CustomScope
AddScreenContact.View providesAddScreenContactView() {
return mView;
}
#Provides
#CustomScope
ContactDatabaseHelper providesContactDatabaseHelper() {
return mContactDatabaseHelper;
}
}
I know that Retrofit class is a final class, and now I stuck and don't know how to create the presenter object in my test class. Please help me, how to create the object of the presenter class with retrofit in the constructor. Feel free to ask if my question is not clear enough, and thank you very much for your help.
Personally I'd make the presenter not depend on the Retrofit class but rather on the services created by Retrofit - These are mockable.
It's hard to say from the code you posted which services your presenter actually uses, but for the sake of simplicity let's say it uses only one and let's say it's AddsService - This is an interface ready to work with Retrofit. Something like this for example
public interface AddsService {
#GET(...)
Call<List<Adds>> getAllAdds();
}
Now you can make your presenter depend on this rather than Retrofit
#Inject
public AddScreenPresenter(AddsService addsService,
AddScreenContact.View view,
ContactDatabaseHelper contactDatabaseHelper){
this.addsService = addsService;
this.view = view;
this.contactDatabaseHelper = contactDatabaseHelper;
}
You now need to provide this dependency. I'm guessing you have also a NetModule since you have a NetComponent, so I assume you can just do:
#Module
public class NetModule {
// Methods providing Retrofit
#Provides
#Singleton
public AddsService providesAddsService(Retrofit retrofit) {
return retrofit.create(AddsService.class);
}
}
Notice how the providesAddsService depends on retrofit? This should be already provided since your presenter is depending on it. You shouldn't need to change anything for that. Dagger is able to figure out how to provide Retrofit to the method providesAddsService.
Please notice also that I'm assuming you can provide these in a Singleton scope. I assume this because in your code you retrieve the component from the application, which should handle the singleton scope.
Now in your tests you can simply mock AddsService and test your presenter.
If your presenter depends on more services, I'd also pass them in the constructor and provide the implementations with Dagger.
As a bonus, let me also say that the retrofit instance and the retrofit services should only be created once (or at least as less times as possible). This is because they're usually expensive operations and you usually always query the same endpoints with different parameters.
EDIT
To answer some of the questions in the comments. First the easy one: How to create the presenter in the test classes? Like you I too try to get away from Dagger during tests, that's why I prefer constructor dependency injection just like you show you're using. So in my test class I'd have something very similar like you:
#RunWith(MockitoJUnitRunner.class)
public class AddScreenPresenterTest {
private AddScreenPresenter mAddPresenter;
#Mock
private AddsService addsService;
// ...
#Before
public void setUp() throws Exception {
mAddPresenter = new AddScreenPresenter(addsService,
mView, mContactDatabaseHelper);
// ...
}
}
So basically the only difference is that I would pass the mock to the service.
Now the second question: How to call the presenter constructor from the activity? Well you don't... that's the whole idea of dependency injection. You should use dagger to provide your presenter. I think this is already what you do and I guess this is what it's in your activity:
#Inject
AddScreenPresenter addScreenPresenter;
So all you need to do is have a provider method in your module that provides this and is able to inject it.
You can also make the component return the presenter provided by the module:
#Component(...)
public interface AddScreenComponent {
AddScreenPresenter getPresenter();
}
And then in your activity you'd do something like:
addScreenPresenter = component.getPresenter();
I don't really have any preference here. The key point is to understand that you should not build the objects yourself (unless inside #Modules). As a rule of thumb any time you see new being used that means you have a tight dependency on that object and you should extract it to be injected. So this is why you should avoid creating the presenter inside your activity. It will couple the presenter to the activity.

Injecting dataSource class to presenter gives null

I'm trying to inject DataSource class to Presenter using Dagger 2, but dataSource is null.
The code is below :
public class MainPresenter implements MainMVP.Presenter {
public static final String TAG = "MAIN-PRESENTER";
#NonNull
private MainMVP.View mainView;
#Inject
DataSource dataSource;
public MainPresenter(#NonNull MainMVP.View mainView) {
this.mainView = mainView;
Log.i(TAG, "MainPresenter init");
DaggerDataComponent.builder()
.dataModule(new DataModule())
.build();
}
#Override
public void onButtonClick() {
if (dataSource != null) {
mainView.showData(dataSource.getReleaseString());
}
}
}
If I remove the condition that checks for null in dataSource I'm getting a NullPointerException. Anyone can help with that? Isn't the constructor the right place to build the DataComponent ?
You're building your component but you don't seem to be actually using it.
DataCompontent component = DaggerDataComponent.builder()
.dataModule(new DataModule())
.build();
component.inject(this);
and add
void inject(MainPresenter presenter);
to your DataComponent interface.
As for your question about if that is the right place to build your component: we can't really answer that. That strongly depends on your code architecture.
Here's a nice example of MVP + dagger2 architecture. Maybe try following that.

How can I replace Activity scoped dependencies with mocks using Dagger2

I have a scoped dependency in my Activity and I want to test that activity with some mocks. I have read about different approach that suggest to replace Application component with a test component during the test, but what I want is to replace the Activity component.
For example, I want to test the Activity against mock presenter in my MVP setup.
I believe that replacing component by calling setComponent() on Activity will not work, because Activity dependencies already injected via field injection, so during the test, real object will be used.
How can I resolve this issue? What about Dagger1? Is it has the same issue?
Injecting the Component
First, you create a static class to act as a factory for your Activity. Mine looks a little like this:
public class ActivityComponentFactory {
private static ActivityComponentFactory sInstance;
public static ActivityComponentFactory getInstance() {
if(sInstance == null) sInstance = new ActivityComponentFactory();
return sInstance;
}
#VisibleForTesting
public static void setInstance(ActivityComponentFactory instance) {
sInstance = instance;
}
private ActivityComponentFactory() {
// Singleton
}
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.create();
}
}
Then just do ActivityComponentFactory.getInstance().createActivityComponent().inject(this); inside your Activities.
For testing, you can replace the factory in your method, before the Activity is created.
Providing mocks
As #EpicPandaForce's answer makes clear, doing this the officially-supported way currently involves a lot of boilerplate and copy/pasted code. The Dagger 2 team need to provide a simpler way of partially overriding Modules.
Until they do though, here's my unnoficial way: Just extend the module.
Let's say you want to replace your ListViewPresenter with a mock. Say you have a PresenterModule which looks like this:
#Module #ActivityScope
public class PresenterModule {
#ActivityScope
public ListViewPresenter provideListViewPresenter() {
return new ListViewPresenter();
}
#ActivityScope
public SomeOtherPresenter provideSomeOtherPresenter() {
return new SomeOtherPresenter();
}
}
You can just do this in your test setup:
ActivityComponentFactory.setInstance(new ActivityComponentFactory() {
#Override
public ActivityComponent createActivityComponent() {
return DaggerActivityComponent.builder()
.presenterModule(new PresenterModule() {
#Override
public ListViewPresenter provideListViewPresenter() {
// Note you don't have to use Mockito, it's just what I use
return Mockito.mock(ListViewPresenter.class);
}
})
.build();
}
});
...and it just works!
Note that you don't have to include the #Provides annotation on the #Override method. In fact, if you do then the Dagger 2 code generation will fail.
This works because the Modules are just simple factories - the generated Component classes take care of caching instances of scoped instances. The #Scope annotations are used by the code generator, but are irrelevant at runtime.
You cannot override modules in Dagger2 [EDIT: you can, just don't specify the #Provides annotation on the mock), which would obviously be the proper solution: just use the builder().somethingModule(new MockSomethingModule()).build() and be done with it!
If you thought mocking is not possible, then I would have seen two possible solutions to this problem. You can either use the modules to contain a pluggable "provider" that can have its implementation changed (I don't favor this because it's just too verbose!)
public interface SomethingProvider {
Something something(Context context);
}
#Module
public class SomethingModule {
private SomethingProvider somethingProvider;
public SomethingModule(SomethingProvider somethingProvider) {
this.somethingProvider = somethingProvider;
}
#Provides
#Singleton
public Something something(Context context) {
return somethingProvider.something(context);
}
}
public class ProdSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new SomethingImpl(context);
}
}
public class TestSomethingProvider implements SomethingProvider {
public Something something(Context context) {
return new MockSomethingImpl(context);
}
}
SomethingComponent somethingComponent = DaggerSomethingComponent.builder()
.somethingModule(new SomethingModule(new ProdSomethingProvider()))
.build();
Or you can bring the provided classes and injection targets out into their own "metacomponent" interface, which your ApplicationComponent and your TestApplicationComponent extend from.
public interface MetaApplicationComponent {
Something something();
void inject(MainActivity mainActivity);
}
#Component(modules={SomethingModule.class})
#Singleton
public interface ApplicationComponent extends MetaApplicationComponent {
}
#Component(modules={MockSomethingModule.class})
#Singleton
public interface MockApplicationComponent extends MetaApplicationComponent {
}
The third solution is to just extend the modules like in #vaughandroid 's answer. Refer to that, that is the proper way of doing it.
As for activity scoped components... same thing as I mentioned here, it's just a different scope, really.
I've found the following post that solves the problem:
http://blog.sqisland.com/2015/04/dagger-2-espresso-2-mockito.html
You need first to allow to modify the component of the activity:
#Override public void onCreate() {
super.onCreate();
if (component == null) {
component = DaggerDemoApplication_ApplicationComponent
.builder()
.clockModule(new ClockModule())
.build();
}
}
public void setComponent(DemoComponent component) {
this.component = component;
}
public DemoComponent component() {
return component;
}
And modify it in the test case
#Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
DemoApplication app
= (DemoApplication) instrumentation.getTargetContext().getApplicationContext();
TestComponent component = DaggerMainActivityTest_TestComponent.builder()
.mockClockModule(new MockClockModule())
.build();
app.setComponent(component);
component.inject(this);
}

In Dagger are Singletons within the sub-graph cached or will they always be recreated when a new activity sub-graph is constructed?

I'm using Dagger to create activity specific object graphs. Within this subgraph, I make use of a Singleton MyPresentationModel.
When i exit my activity, and enter the activity again, my expectation is that a new instance of the activity specific object graph is created, which in turn would create a new instance of Singleton MyPresentationModel (by virtue of the #Singleton semantic per Dagger. See this So answer for specifics) which would then last for the life of the activity specific object graph.
However, this is not what i'm observing, every time the activity specific object graph is created, the same instance of MyPresentationModel is used. I added a debug point into the constructor of MyPresentationModel. The very first time we enter the constructor. Subsequently even on activity exits and reentries, we don't enter the constructor (and because of this the UserSession being used within my Presentation model uses the old value from the very first constructor injection).
While i can technically solve the problem by re-setting UserSession inside MyPresentaitonModel with an external public setter, I want to understand better the mechanics of the activity specific object graph creation/destruction.
By nullifying the graph in my onDestroy, does that still mean that there is a possibility of the Singletons within my subgraph being reused at a later point ? (possibly until they are truly GCed?)
Here's some code:
// MyAppModule
#Module(
includes = { UserSession.class},
injects = { MyApplication.class })
public class MyAppModule {
private final MyApplication _app;
MyAppModule(MyApplication app) {
_app = app;
}
// ...
}
// Main Activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
_activityObjectGraph = MyApplication.get()
.getObjectGraph()
.plus(Arrays.<Object>asList(new SubModule()).toArray());
// Inject ourselves so subclasses will have dependencies fulfilled when this method returns.
_activityObjectGraph.inject(this);
}
#Override
protected void onDestroy() {
_activityObjectGraph = null;
// this eagerly allows GC, but doesn't necessarily destroy the subgraph ?
super.onDestroy();
}
// SubModule
#Module(injects = { MyPresentationModel.class, MainActivity.class },
addsTo = MyAppModule.class,
library = true)
public class SubModule {}
}
// MyPresentationModel
#Singleton
public class MyPresentationModel {
private UserSession _session;
#Inject
public MyPresentationModel(UserSession session) {
_session = session;
}
public void someMethodThatUsesSessionInfo() {
// _session.getUser() ...
}
}
#weefbellington posted a very informative answer, but reading it made me realize my question was not specific and clear enough. Here's attempt 2:
MyAppModule (main graph) -> provides a Singleton UserSession
MySubModule (sub graph plused onto MyAppModule) -> provides "activity specific" Singleton MyPresentationModel which requires a UserSession (provided my MyAppModule) on construction.
I now close the activity, destroying MySubModule (and also hopefully MyPresentationModel which is a Singleton), I update UserSession with some new information.
I open MainActivity again, thus re-creating the sub-graph from MySubModule, which inturn provides a MyPresentationModel.
The issue I'm noticing is that MyPresentationModel which is the local Singleton is not being reconstructed again i.e. this part of the code:
#Inject
public MyPresentationModel(UserSession session) {
_session = session;
}
is only ever being called once. My expectation was that this part of the code would be run again, and the UserSession would be pulled again from the Main graph and since it was updated, it would hold the updated values. My question is: are Singletons within the sub-graph cached in anyway or will they always be recreated when a new activity sub-graph is spawned?
How MyPresentationModule is injected depends on how your modules are specified. For example, assume that you are injecting the class Foo:
public class Foo {
private final MyPresentationModel model;
#Inject
public Foo(MyPresentationModel model) {
this.model = model;
}
}
If your modules are structured like (A), then the MyPresentationModel singleton will be injected into Foo by the main object graph:
EXAMPLE A
#Module(injects = { Foo.class })
public class MainModule { ... }
#Module(addsTo = MainModule.class, injects = { MyPresentationModel.class })
public class SubModule { ...}
Alternatively, if your modules are structured like (B), then the MyPresentationModel singleton will be injected into Foo by the subgraph:
EXAMPLE B
#Module
public class MainModule { ... }
#Module(addsTo = MainModule.class, injects = { Foo.class, MyPresentationModel.class })
public class SubModule { ... }
In your particular case, since you have specified that MyAppModule injects MyApplication, I would guess that you are trying to inject MyPresentationModel into your Application class. This is probably not what you want to do. You probably want inject this into your Activity class using the submodule, as in (C).
EXAMPLE C
#Module(injects = { MainActivity.class, MyPresentationModel.class },
addsTo = MyAppModule.class,
library = true)
public class SubModule { ... }
public class MainActivity {
#Inject MyPresentationModel presentationModel;
...
}
If you do this the MyPresentationModel singleton will be bound to the Activity subgraph instead of the main graph, and should be disposed when the Activity is destroyed.
Once you have a handle on Dagger, you might want to check out Mortar, which gives you finer-grained control over creation and destruction of ObjectGraph subscopes.

Roboguice 2.0 (android): POJO injection error (always null)

My base POJO class:
public class BaseDao {
public BaseDao() {
}
// ...
}
My extends POJO class:
public class KelvinDao extends BaseDao {
public KelvinDao () {
super();
}
// ...
}
I want to use KelvinDao in a service like that:
public class HanKelvinHandler extends HttpRequestHandler {
#Inject
private KelvinDao mKelvinDao;
public void treatGet() {
mKelvinDao.blabla(); !!! mKelvinDao is always NULL
}
It's really simple but it doesn't work :(
Thank you guys for your help!
How are you creating HanKelvinHandler? If you're doing it within a subclass of a RoboGuice class, such as RoboActivity, then it should just work. Example:
public class MyActivity extends RoboActivity
{
#Inject
private HanKelvinHandler m_handler;
[...]
}
Otherwise (i.e., you're creating it within another POJO), you're in regular Guice land, and I believe you will need to use the injector to get an instance of it. Example:
public class MyClass
{
public void doSomething()
{
Injector injector = Guice.createInjector( new YourGuiceBindings() );
HanKelvinHandler handler = injector.getInstance( HanKelvinHandler.class );
handler.treatGet(); // mKelvinDao should be good now
}
}
If you haven't seen the use of the injector before, or you don't know what to put for YourGuiceBindings(), then you may need to read the following:
https://github.com/roboguice/roboguice/wiki/Simple-Custom-Binding
https://code.google.com/p/google-guice/wiki/GettingStarted
It seems like there should be a way to do this without using the injector, but I don't know.

Categories

Resources