With the latest Dagger2 we can inject dependencies through the following code:
public class BaseActivity extends LifecycleActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
}
Other activities that require DI inherit from this BaseClass.
For my instrumentation tests, I don't want to use Dagger, I can just mock the objects.
My setup for the instrumentation test (I'm currently testing LoginActivity, which extends BaseActivity) is not special and as follows:
TestRunner:
public class LivefeedTestRunner extends AndroidJUnitRunner{
#Override
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return super.newApplication(cl, TestApp.class.getName(), context);
}
}
TestApp:
public class TestApp extends Application {
#Override
public void onCreate() {
super.onCreate();
}
}
The problem is that running the instrumentation test results in an error caused by:
Caused by: java.lang.RuntimeException: com.example.kimgysen.livefeed_v002.TestApp does not implement dagger.android.HasActivityInjector
at dagger.android.AndroidInjection.inject(AndroidInjection.java:48)
at com.example.kimgysen.livefeed_v002.ui.BaseActivity.onCreate(BaseActivity.java:12)
at com.example.kimgysen.livefeed_v002.ui.login.LoginActivity.onCreate(LoginActivity.java:32)
at android.app.Activity.performCreate(Activity.java:6237)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
at android.support.test.runner.MonitoringInstrumentation.callActivityOnCreate(MonitoringInstrumentation.java:624)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
... 9 more
Which points to the following line:
public class BaseActivity extends LifecycleActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this); // <-- this line
super.onCreate(savedInstanceState);
}
}
Any idea how I can easily bypass this?
In my app itself, I implement dagger.android.HasActivityInjector, but I don't need it for my instrumentation test.
I solved the problem by creating a fake activityInjector as suggested in the following post:
https://github.com/google/dagger/blob/master/javatests/dagger/android/AndroidInjectionTest.java#L60
(equivalent for fakeActivityInjector)
public class TestApp extends Application implements HasActivityInjector {
#Override
public void onCreate() {
super.onCreate();
}
#Override
public AndroidInjector<Activity> activityInjector() {
return fakeActivityInjector("injected by app");
}
public static class InjectableActivity extends Activity {
String tag;
}
private static AndroidInjector<Activity> fakeActivityInjector(String tag) {
return instance -> {
if (instance instanceof InjectableActivity) {
((InjectableActivity) instance).tag = tag;
}
};
}
}
There is an alternative solution in the android-architecture-components samples on Github.
They inject the activities trough ActivityLifecycleCallbacks. For instrumented tests they use a TestApp that does not register ActivityLifecycleCallbacks so it injects nothing.
See https://stackoverflow.com/a/48101811/1552622.
Related
I am trying to inject my MainActivity into the Fragment. I have an Interface that is implemented in my MainActivity that will listen to events from the Fragment. So I want to Inject the MainActivity and call the event listener on it.
I have tried doing the following but has failed to do so. Just displaying the code snippets.
interface
public interface RecipeItemListener {
void onRecipeItem();
}
MainActivity that implements the interface
public class MainActivity extends AppCompatActivity implements RecipeItemListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.add(R.id.main_fragment_container, RecipeListView.newInstance(), RecipeListView.TAG)
.commit();
}
}
#Override
public void onRecipeItem() {
Timber.d("onRecipeItem");
}
}
My Module that provides the MainActivity
#Module
public class RecipeListModule {
private MainActivity mainActivity;
public RecipeListModule(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
#RecipeListScope
#Provides
public RecipeItemListener providesRecipeListMainActivity() {
return mainActivity;
}
}
My main Component
#Singleton
#Component(modules = {
AndroidModule.class,
NetworkModule.class})
public interface BusbyBakingComponent {
RecipeListComponent add(RecipeListModule recipeListModule);
}
My SubComponent
#RecipeListScope
#Subcomponent(modules = {RecipeListModule.class})
public interface RecipeListComponent {
void inject(RecipeListView target);
}
My Application class
public class BusbyBakingApplication extends Application {
private BusbyBakingComponent applicationComponent;
private RecipeListComponent recipeListComponent;
#Override
public void onCreate() {
super.onCreate();
applicationComponent = createApplicationComponent();
}
public BusbyBakingComponent createApplicationComponent() {
return DaggerBusbyBakingComponent.builder()
.networkModule(new NetworkModule())
.androidModule(new AndroidModule(BusbyBakingApplication.this))
.build();
}
public BusbyBakingComponent getApplicationComponent() {
return applicationComponent;
}
public RecipeListComponent createRecipeListComponent(MainActivity activity) {
recipeListComponent = applicationComponent.add(new RecipeListModule(activity));
return recipeListComponent;
}
public void releaseRecipeListComponent() {
recipeListComponent = null;
}
}
And in My fragment this is how I am trying to inject:
#Inject MainActivity mainActivity;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((BusbyBakingApplication)getActivity().getApplication())
.createRecipeListComponent((MainActivity)getActivity())
.inject(RecipeListView.this);
}
I keep getting the following error:
Error:(14, 8) error: [me.androidbox.busbybaking.di.RecipeListComponent.inject(me.androidbox.busbybaking.recipieslist.RecipeListView)] me.androidbox.busbybaking.recipieslist.MainActivity cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
me.androidbox.busbybaking.recipieslist.MainActivity is injected at
me.androidbox.busbybaking.recipieslist.RecipeListView.mainActivity
me.androidbox.busbybaking.recipieslist.RecipeListView is injected at
me.androidbox.busbybaking.di.RecipeListComponent.inject(target)
Many thanks for any suggestions.
If you have a look at your module
#RecipeListScope
#Provides
public RecipeItemListener providesRecipeListMainActivity() {
return mainActivity;
}
You provide the interface (which is good) and not MainActivity (the implementation).
Since you request MainActivity
#Inject MainActivity mainActivity;
You receive this error:
MainActivity cannot be provided [...]
because you only provide RecipeItemListener.
Switch your code from requiring MainActivity in RecipeListView to
#Inject RecipeItemListener recipeListener;
and it should work, if the rest of your setup is correct.
You can access activity in Fragment using getActivity() and cast it to your interface listener like this
((RecipeItemListener)getActivity()).doSomethingOnListener()
much simpler, without any unnecessary injections
i'm trying to update this below code to dagger2, but i get error for ObjectGraph:
import dagger.ObjectGraph;
public class App extends Application {
private static App instance;
private ObjectGraph objectGraph;
public App() {
instance = this;
}
#Override
public void onCreate() {
super.onCreate();
objectGraph = ObjectGraph.create(new AppModule());
}
public static void injectMembers(Object object) {
getInstance().objectGraph.inject(object);
}
public static <T>T get(Class<T> klass) {
return getInstance().objectGraph.get(klass);
}
public static App getInstance() {
return instance;
}
}
how can i update that to and which class must be use instead of ObjectGraph?
injectMembers used in this class
public class MyJobManager extends JobManager {
public MyJobManager(Context context) {
super(context, new Configuration.Builder(context)
.injector(new DependencyInjector() {
#Override
public void inject(BaseJob baseJob) {
App.injectMembers(baseJob);
}
})
.build());
}
}
now how can i inject with component?
my component:
#ActivitiesScope
#Component(dependencies = GithubApplicationComponent.class)
public interface ApplicationComponent {
void inject(ActivityRegister activityRegister);
void inject(ActivityStartUpApplication activityStartUpApplication);
void inject(GetLatestRepositories getLatestRepositories);
}
Dagger 2 doesn't use an ObjectGraph. It doesn't use anything as its replacement. Dagger1 did injection at runtime via reflection and used the ObjectGraph to provide that functionality. Dagger 2 does injection at compile time, thus it doesn't need a runtime object to represent the graph. Instead you'd want to build a component that links the modules you with to provide. You can then inject using that component.
See https://google.github.io/dagger/dagger-1-migration.html for more details.
In Dagger 1 I had a base class setup such that it would handle creating a scoped graph and injecting dependencies into the current object. For example...
public abstract class MyBaseActivity extends Activity {
private ObjectGraph graph;
protected void onCreate(Bundle savedInstanceState) {
graph = ((MyApp) getApplication()).plus(getModules());
graph.inject(this);
}
protected Object[] getModules();
}
public class MyClass extends MyBaseActivity {
#Inject SomeDep someDep;
#Override
protected Object[] getModules() {
return new Object[/* Contains a module that provides SomeDep */];
}
}
This allowed for each subclass to supplement their own set of modules in addition to a standard application module.
After playing around with Dagger 2, it doesn't seem possible to handle a similar scenario...
public abstract class MyBaseActivity extends Activity {
private MyBaseActivityComponent component;
protected void onCreate(Bundle savedInstanceState) {
component = ((MyApp) getApplication()).component().plus(/* Can not accept an array */);
component.inject(this);
}
}
I worked around the above by modifying MyBaseActivityComponent such that it would list all possible modules it may use...
#Subcomponent(modules = {
Module1.class,
Module2.class
})
public interface MyBaseActivityComponent {
public void inject(MyBaseActivity activity);
}
So now I can do something like this...
public abstract class MyBaseActivity extends Activity {
private MyBaseActivityComponent component;
protected void onCreate(Bundle savedInstanceState) {
component = ((MyApp) getApplication()).component().plus(new Module1(), new Module2());
component.inject(this);
}
}
But now I have a problem where the injection will inject dependencies for MyBaseActivity but not it's subclasses. Suggestions?
Theoretically, you can do it like this.
1.) Specify a child scope
#Scope
#Retention(RUNTIME)
public #interface PerActivity {
}
2.) Specify the parent component
#Singleton
#Component(modules={Module1.class, Module2.class)
public interface MyApplicationComponent {
Dependency1 providesDependency1();
Dependency2 providesDependency2();
}
3.) Specify the child component
#PerActivity
#Component(dependencies={MyApplicationComponent.class}, modules={Module3.class})
public interface MyBaseActivityComponent extends MyApplicationComponent {
void inject(BaseActivity baseActivity);
Dependency3 providesDependency3();
}
4.) Create your module
#Module
public class Module3 {
#Provides
#PerActivity
public Dependency3 providesDependency3() {
return new Dependency3();
}
}
5.) Create Activity-level scoped component
public class BaseActivity extends AppCompatActivity {
private MyBaseActivityComponent baseComponent;
#Override
public void onCreate(Bundle saveState) {
super.onCreate(saveState);
baseComponent = DaggerBaseActivityComponent.builder()
.applicationComponent(((MyApp)getApplication()).component())
.build();
}
public MyBaseActivityComponent baseComponent() {
return baseComponent;
}
#Override
public void onDestroy() {
component = null;
super.onDestroy();
}
}
Please reply if it worked, previously I forgot to specify the dependencies in my Component and got compile errors, but it should work like this.
Also, if you need to specify a subcomponent for each Activity, then you can just specify the dependencies with provision methods in the BaseActivityComponent component...
#PerActivity
#Component(dependencies={MyBaseActivityComponent.class}, modules={Module4.class})
public interface MyActivityComponent extends MyBaseActivityComponent {
public void inject(MyActivity myActivity);
Dependency4 providesDependency4();
}
#Module
public class Module4 {
#PerActivity
#Provides
public Dependency4 providesDependency4(Dependency3 dependency3) {
return new Dependency4(dependency3);
}
}
public class MyActivity extends MyBaseActivity {
private MyActivityComponent component;
#Override
public void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
component = DaggerMyActivityComponent.builder()
.applicationComponent(((MyApp)getApplication()).component())
.myBaseActivityComponent(baseComponent())
.build();
}
#Override
public void onDestroy() {
component = null;
super.onDestroy();
}
}
EDIT: #Subcomponent works to replace component dependencies with subcomponent factory methods according to the docs only if you use the following pattern (aka, embedding the subcomponent within the parent component using a provision/factory method definition):
#Singleton #Component
interface ApplicationComponent {
// component methods...
RequestComponent newRequestComponent(RequestModule requestModule);
}
Where
#Subcomponent(modules={RequestModule.class})
interface RequestComponent {
RequestSomething requestSomething();
}
I am working on an app which uses the NavigationDrawer. Different fragments are placed into the content view of the MainActivity whenever a menu item in the drawer is selected.
To inform the MainActivity that a Fragment successfully attached the following callback is executed:
public class CustomFragment extends Fragment {
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((MainActivity) activity).onSectionAttached();
}
}
Since I am started using Otto with Dagger in the project I am curious how I can substitute the callback with a .post() event such as:
mBus.post(new CustomFragmentAttachedEvent);
The problem is that mBus is null in onAttach(). It gets initialized in onCreate().
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApp) getActivity().getApplication()).getObjectGraph().inject(this);
}
Here is an example of such a Fragment class.
References:
Complete Android Fragment & Activity Lifecycle
You can easily try out the example yourself: Create a new project from the NavigationDrawer template available in Android Studio, add Dagger and Otto and try to substitute the mentioned callback.
Working solution:
Here is the example Fragment in the final working version.
You can create a provider for your Otto bus with Dagger as follows:
#Module(injects = {
YourFragment.class,
MainActivity.class
},complete = true)
public class EventBusModule {
#Provides
#Singleton
Bus provideBus() {
return new Bus();
}
}
Then you register the EventBusModule when you create your ObjectGraph. You can create your graph in the Application's onCreate():
public class MyApplication extends Application{
public void onCreate() {
Object[] modules = new Object[]{new EventBusModule()};
Injector.init(modules);
}
}
You would need to create an Injector that has some static methods and a reference to the ObjectGraph so you can manipulate it without having a reference to the Application. Something like this:
public final class Injector {
private static ObjectGraph objectGraph = null;
public static void init(final Object... modules) {
if (objectGraph == null) {
objectGraph = ObjectGraph.create(modules);
}
else {
objectGraph = objectGraph.plus(modules);
}
// Inject statics
objectGraph.injectStatics();
}
public static final void inject(final Object target) {
objectGraph.inject(target);
}
public static <T> T resolve(Class<T> type) {
return objectGraph.get(type);
}
}
Then you just use #Inject in your Fragment to let Dagger give you a Bus instance and inject the Fragment:
public class MyFragment extends Fragment{
#Inject Bus mBus;
public void onAttach(){
Injector.inject(this);
}
}
Is it possible to Inject object exposed through dagger into android.app.IntentService?
If so, how I can do that?
I want to have something like that.
public class SomeService extends android.app.IntentService {
#Inject
Synchronizer synchronizer;
public SomeService(String name) {
super(name);
}
#Override
protected void onHandleIntent(Intent intent) {
synchronizer.doSynch();
}
}
From a dagger point of view, IntentService isn't different from any other class.
However Dagger 2.x provides a new way, how you can inject dependencies into Android's base building element (like Service, Activities, Fragment etc.). Here sample calls for both versions.
Dagger 1.x
Injection can look like that (I'm assuming, that your Application has an instance of ObjectGraph and exposes method inject). Of course, don't forget add the class into a injected classes list in your Module definition.
public class SomeService extends android.app.IntentService {
#Inject
Synchronizer synchronizer;
public SomeService(String name) {
super(name);
}
#Override
public void onCreate() {
super.onCreate();
((YourApplication) getApplication()).inject(this);
}
#Override
protected void onHandleIntent(Intent intent) {
synchronizer.doSynch();
}
}
Dagger 2.10+
Dagger 2.10 has introduced AndroidInjection so no further dependency on the concrete Application instance is required.
public class SomeService extends android.app.IntentService {
#Inject
Synchronizer synchronizer;
public SomeService(String name) {
super(name);
}
#Override
public void onCreate() {
super.onCreate();
AndroidInjection.inject(this);
}
#Override
protected void onHandleIntent(Intent intent) {
synchronizer.doSynch();
}
}