How to use dependecy injection in flutter? - android

I wan to use di. in flutter and I add this https://pub.dartlang.org/packages/di packages my project and I started to read this https://webdev.dartlang.org/angular/guide/dependency-injection article, but I don't fully understand.
So it's ok:
use #Injectable() annotation on services class(e.g: MyServices), but how to inject other class?
For example I would like to similar:
class MyClass{
//some variable
var asd = MyService.asd; //need inject var.
//maybe use injector.get(MyService).asd;
//but how add injector? (I don't add across constructor)
MyService.oneMethod;//need inject method
}
main(){
var injector = new ModuleInjector([new Module()
..bind(MyService)
]);
}
The point is, I don't want to use a constructor. I want to directly use injector. This is possible in flutter/dart?

There is a Flutter-compatible dependency injection framework that Google recently open-sourced. It is available here: https://github.com/google/inject.dart
This project provides static compile-time dependency injection, rather than relying on reflection to inject dependencies at run-time. If you are familiar with Dagger, it appears to be quite similar.
It's worth noting - this is not an official Google or Dart team project. At the time of writing, there is little documentation and it is currently considered a developer preview.

Angulars DI package can't be used independent of Angular.
The di package is quite outdated and depends on dart:mirrors which isn't available in Flutter
There seems to be a new DI package work in progress which is supposed to replace Angulars built-in DI and should also be useable standalone in Flutter or server-side applications, but there is no pre-release or source code available yet.
update
The announced DI package was delayed indefinitely.

If you want to use a class as a Singleton, from what I saw, the common usecase is to use a factory constructor.
How do you build a Singleton in Dart?
Never had to use any dependency injection system with Flutter for now.

Hello what about something like this? Very simple implementation, Injector itself is singleton and also added classes into it. Of course can be extended very easily. If you are looking for something more sophisticated check this package: https://pub.dartlang.org/packages/flutter_simple_dependency_injection
void main() {
Injector injector = Injector();
injector.add(() => Person('Filip'));
injector.add(() => City('New York'));
Person person = injector.get<Person>();
City city = injector.get<City>();
print(person.name);
print(city.name);
}
class Person {
String name;
Person(this.name);
}
class City {
String name;
City(this.name);
}
typedef T CreateInstanceFn<T>();
class Injector {
static final Injector _singleton = Injector._internal();
final _factories = Map<String, dynamic>();
factory Injector() {
return _singleton;
}
Injector._internal();
String _generateKey<T>(T type) {
return '${type.toString()}_instance';
}
void add<T>(CreateInstanceFn<T> createInstance) {
final typeKey = _generateKey(T);
_factories[typeKey] = createInstance();
}
T get<T>() {
final typeKey = _generateKey(T);
T instance = _factories[typeKey];
if (instance == null) {
print('Cannot find instance for type $typeKey');
}
return instance;
}
}

I was facing the same issue here so I created a dart package specifically for Flutter. It relies on factory methods rather than the mirrors api so it works in flutter. While still using a familiar IOC pattern. Hope this helps!
https://pub.dartlang.org/packages/flutter_simple_dependency_injection
import 'package:flutter_simple_dependency_injection/injector.dart';
void main() {
final injector = Injector.getInjector();
injector.map(Logger, (i) => new Logger(), isSingleton: true);
injector.map(String, (i) => "https://api.com/", key: "apiUrl");
injector.map(SomeService, (i) => new SomeService(i.get(Logger), i.get(String, "apiUrl")));
injector.get(SomeService).doSomething();
// passing in the [SomeService] as a generic parameter strongly types the return object.
injector.get<SomeService>(SomeService).doSomething();
}
class Logger {
void log(String message) => print(message);
}
class SomeService {
final Logger _logger;
final String _apiUrl;
SomeService(this._logger, this._apiUrl);
void doSomething() {
_logger.log("Doing something with the api at '$_apiUrl'");
}
}

This package ioc_container is a Dart Ioc Container. Technically, it's not an injector because it doesn't automate injection with anything like reflection. However, it does simplify and organize the process of injecting dependencies within your factories. Here is the code.
Code here
import 'package:meta/meta.dart';
class ServiceDefinition<T> {
bool isSingleton;
T Function(IocContainer container) factory;
ServiceDefinition(this.isSingleton, this.factory);
}
class IocContainer {
#visibleForTesting
final Map<Type, ServiceDefinition> serviceDefinitionsByType;
#visibleForTesting
final Map<Type, Object> singletons;
IocContainer(this.serviceDefinitionsByType, this.singletons);
///Get an instance of your dependency
T get<T>() => singletons.containsKey(T)
? singletons[T] as T
: serviceDefinitionsByType.containsKey(T)
? (serviceDefinitionsByType[T]!.isSingleton
? singletons.putIfAbsent(
T,
() =>
serviceDefinitionsByType[T]!.factory(this) as Object) as T
: serviceDefinitionsByType[T]!.factory(this))
: throw Exception('Service not found');
}
///A builder for creating an [IocContainer].
class IocContainerBuilder {
final Map<Type, ServiceDefinition> _serviceDefinitionsByType = {};
///Throw an error if a service is added more than once. Set this to true when
///you want to add mocks to set of services for a test.
final bool allowOverrides;
IocContainerBuilder({this.allowOverrides = false});
///Add a factory to the container.
void addServiceDefinition<T>(
///Add a factory and whether or not this service is a singleton
ServiceDefinition<T> serviceDefinition) {
if (_serviceDefinitionsByType.containsKey(T)) {
if (allowOverrides) {
_serviceDefinitionsByType.remove(T);
} else {
throw Exception('Service already exists');
}
}
_serviceDefinitionsByType.putIfAbsent(T, () => serviceDefinition);
}
///Create an [IocContainer] from the [IocContainerBuilder].
///This will create an instance of each singleton service and store it
///in an immutable list unless you specify [isLazy] as true.
IocContainer toContainer(
{
///If this is true the services will be created when they are requested
///and this container will not technically be immutable.
bool isLazy = false}) {
if (!isLazy) {
final singletons = <Type, Object>{};
final tempContainer = IocContainer(_serviceDefinitionsByType, singletons);
_serviceDefinitionsByType.forEach((type, serviceDefinition) {
if (serviceDefinition.isSingleton) {
singletons.putIfAbsent(
type, () => serviceDefinition.factory(tempContainer));
}
});
return IocContainer(
Map<Type, ServiceDefinition>.unmodifiable(_serviceDefinitionsByType),
Map<Type, Object>.unmodifiable(singletons));
}
return IocContainer(
Map<Type, ServiceDefinition>.unmodifiable(_serviceDefinitionsByType),
<Type, Object>{});
}
}
extension Extensions on IocContainerBuilder {
///Add a singleton object dependency to the container.
void addSingletonService<T>(T service) =>
addServiceDefinition(ServiceDefinition(true, (i) => service));
void addSingleton<T>(T Function(IocContainer container) factory) =>
addServiceDefinition(ServiceDefinition(true, factory));
void add<T>(T Function(IocContainer container) factory) =>
addServiceDefinition(ServiceDefinition(false, factory));
}

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.

Android Tests: use Dagger2 + Gradle

I understand how Dagger2 works,
I understand it allows to easily swap dependencies, so we can use mocks for testing.
Point is that I am not sure I understand how am I supposed to provide different Dagger2 Components implementations for testing and for debug/production.
Would I need to create 2 Gradle productFlavors (e.g "Production"/"Test")
that would contain 2 different Components definition?
Or can I specify that I want to use the mock Component for test compile and the non mock Component for non test builds?
I am confused, please some clarification would be great!
Thanks a lot!
Unit testing
Don’t use Dagger for unit testing
For testing a class with #Inject annotated constructor you don't need dagger. Instead create an instance using the constructor with fake or mock dependencies.
final class ThingDoer {
private final ThingGetter getter;
private final ThingPutter putter;
#Inject ThingDoer(ThingGetter getter, ThingPutter putter) {
this.getter = getter;
this.putter = putter;
}
String doTheThing(int howManyTimes) { /* … */ }
}
public class ThingDoerTest {
#Test
public void testDoTheThing() {
ThingDoer doer = new ThingDoer(fakeGetter, fakePutter);
assertEquals("done", doer.doTheThing(5));
}
}
Functional/integration/end-to-end testing
Functional/integration/end-to-end tests typically use the production
application, but substitute fakes[^fakes-not-mocks] for persistence,
backends, and auth systems, leaving the rest of the application to
operate normally. That approach lends itself to having one (or maybe a
small finite number) of test configurations, where the test
configuration replaces some of the bindings in the prod configuration.
You have two options here:
Option 1: Override bindings by subclassing modules
#Component(modules = {AuthModule.class, /* … */})
interface MyApplicationComponent { /* … */ }
#Module
class AuthModule {
#Provides AuthManager authManager(AuthManagerImpl impl) {
return impl;
}
}
class FakeAuthModule extends AuthModule {
#Override
AuthManager authManager(AuthManagerImpl impl) {
return new FakeAuthManager();
}
}
MyApplicationComponent testingComponent = DaggerMyApplicationComponent.builder()
.authModule(new FakeAuthModule())
.build();
Option 2: Separate component configurations
#Component(modules = {
OAuthModule.class, // real auth
FooServiceModule.class, // real backend
OtherApplicationModule.class,
/* … */ })
interface ProductionComponent {
Server server();
}
#Component(modules = {
FakeAuthModule.class, // fake auth
FakeFooServiceModule.class, // fake backend
OtherApplicationModule.class,
/* … */})
interface TestComponent extends ProductionComponent {
FakeAuthManager fakeAuthManager();
FakeFooService fakeFooService();
}
More about it in the official documentation testing page.

Mockito and callback returning "Argument(s) are different!"

I'm trying to use mockito on android. I want to use it with some callback.
Here my test :
public class LoginPresenterTest {
private User mUser = new User();
#Mock
private UsersRepository mUsersRepository;
#Mock
private LoginContract.View mLoginView;
/**
* {#link ArgumentCaptor} is a powerful Mockito API to capture argument values and use them to
* perform further actions or assertions on them.
*/
#Captor
private ArgumentCaptor<LoginUserCallback> mLoadLoginUserCallbackCaptor;
private LoginPresenter mLoginPresenter;
#Before
public void setupNotesPresenter() {
// Mockito has a very convenient way to inject mocks by using the #Mock annotation. To
// inject the mocks in the test the initMocks method needs to be called.
MockitoAnnotations.initMocks(this);
// Get a reference to the class under test
mLoginPresenter = new LoginPresenter(mUsersRepository, mLoginView);
// fixtures
mUser.setFirstName("Von");
mUser.setLastName("Miller");
mUser.setUsername("von.miller#broncos.us");
mUser.setPassword("Broncos50superBowlWinners");
}
#Test
public void onLoginFail_ShowFail() {
// When try to login
mLoginPresenter.login("von.miller#broncos.us", "notGoodPassword");
// Callback is captured and invoked with stubbed user
verify(mUsersRepository).login(eq(new User()), mLoadLoginUserCallbackCaptor.capture());
mLoadLoginUserCallbackCaptor.getValue().onLoginComplete(eq(mUser));
// The login progress is show
verify(mLoginView).showLoginFailed(anyString());
}
But I got this error :
Argument(s) are different! Wanted:
mUsersRepository.login(
ch.example.project.Model.User#a45f686,
<Capturing argument>
);
-> at example.ch.project.Login.LoginPresenterTest.onLoginFail_ShowFail(LoginPresenterTest.java:94)
Actual invocation has different arguments:
mUsersRepository.login(
ch.example.project.Model.User#773bdcae,
ch.example.project.Login.LoginPresenter$1#1844b009
);
Maybe the issue is that the second actual argument is ch.example.project.Login.LoginPresenter$1#1844b009 ?
I followed : https://codelabs.developers.google.com/codelabs/android-testing/#5
Thank you for help =)
Edit
The method I try to test (LoginPresenter):
#Override
public void login(String email, String password) {
mLoginView.showLoginInProgress();
User user = new User();
user.setUsername(email);
user.setPassword(password);
mUsersRepository.login(user, new UsersRepository.LoginUserCallback() {
#Override
public void onLoginComplete(User loggedUser) {
mLoginView.showLoginComplete();
}
#Override
public void onErrorAtAttempt(String message) {
mLoginView.showLoginFailed(message);
}
});
}
eq(new User())
When using eq (or not using matchers at all), Mockito compares arguments using the equals method of the instance passed in. Unless you've defined a flexible equals implementation for your User object, this is very likely to fail.
Consider using isA(User.class), which will simply verify that the object instanceof User, or any() or anyObject() to skip matching the first parameter entirely.
I am using mvp pattern with rxjava 2 and dagger 2, and was stuck on unit testing a presenter using Mockito. The code that gave me the "Argument(s) are different!” Error:
#Mock
ImageService imageService;
#Mock
MetadataResponse metadataResponse;
private String imageId = "123456789";
#Test
public void getImageMetadata() {
when(imageService.getImageMetadata(imageId)).thenReturn(Observable.just(Response.success(metadataResponse)));
presenter.getImageMetaData(imageId);
verify(view).showImageData(new ImageData()));
}
Which throws error messages such as the following:
Argument(s) are different! Wanted: Actual invocation has different
arguments: com.example.model.ImageData#5q3v861
Thanks to the answer from #Jeff Bowman, it worked after I changed this line
verify(view).showImageData(new ImageData()));
with
verify(view).showImageData(isA(ImageData.class));

Map enums to Dagger injected classes (MapBinder equivalent)

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 {
}

Dagger not injecting Android Annotations class

Alright, I'm having an issue trying to mix frameworks.
So, I have a #SharedPref annotated class that should generate a Shared Preferences manager from Android Annotations. The class looks a bit something like this:
DownloadPrefs.java
#SharedPref(value= SharedPref.Scope.UNIQUE)
public interface DownloadPrefs {
#DefaultBoolean(false)
boolean hasEnabledDownload();
#DefaultBoolean(false)
boolean showedDownloadDialog();
#DefaultLong(0)
long downloadRefreshedOn();
}
Now, I'd like to inject the resulting class (which will be DownloadPrefs_) into a Fragment to make use of it. The fragment has had working injection before adding the new module, so I'm only going to write here what I added:
Fragment.java
#Inject DownloadPrefs_ downloadPrefs;
Now, since the actual DownloadPrefs_ class is generated at runtime, it would make the most sense to create an #Provides annotation for it, since I can't mark a constructor as injected. Nor does the DownloadPrefs_ have a no-arg constructor. The module I'm using then receives the new #Provides:
DownloaderModule.java
#Provides //#Singleton // Does not work with/out #Singleton
DownloadPrefs_ provideDownloadPrefs() {
return new DownloadPrefs_(MinimalBible.getApplication());
}
To be technical about it, the DownloadPrefs_ constructor that gets generated by Android Annotations expects a Context passed to it, I would have guessed that the Application context would be suitable. Otherwise, I'm not sure how I could possibly get access to the Activity context. Or whether that would actually break the ObjectGraph.
However, when I go to run the actual injection, I get the following message:
Caused by: java.lang.IllegalStateException: Errors creating object graph:
org.bspeice.minimalbible.activities.downloader.DownloadPrefs_ has no injectable members. Do you want to add an injectable constructor? required by class org.bspeice.minimalbible.activities.downloader.BookListFragment
Any clue on what's going on? It doesn't seem like the questions asking about "no injectable members" on other SO questions answered my case. I had a working app before adding the code above.
UPDATE: After doing some double-checking, I came across the following weird behavior. If I copy out the pre-built Android Annotations class, rename it, and inject that, everything works. Additionally, I can verify that the original built Android Annotations class (the DownloadPrefs_.java) does in fact exist in the .dex, so Dagger should have no reason to not be able to find it. Everything is doing a debug build, so I can't imagine ProGuard is messing anything up.
At this point, I'm going to create a minimal project to demonstrate the error, and file an issue with Dagger. In the mean time, just need to rewrite the Prefs class until I can get this sorted out.
UPDATE 5/12/2014
Here are the modules responsible for injection:
MinimalBibleModules.java
#Module(
injects = {
MinimalBible.class
},
includes = {
ActivityModules.class
}
)
public class MinimalBibleModules {
}
ActivityModules.java
#Module(
includes = {
ActivityDownloaderModule.class
}
)
public class ActivityModules {
}
ActivityDownloaderModule.java
#Module(
injects = {
BookListFragment.class,
DownloadManager.class,
BookRefreshTask.class
}
)
public class ActivityDownloaderModule {
#Provides #Singleton
DownloadManager provideDownloadManager() {
return new DownloadManager();
}
#Provides
EventBus provideBus() {
return new EventBus();
}
#Provides //#Singleton
DownloadPrefs_ provideDownloadPrefs() {
return new DownloadPrefs_(MinimalBible.getApplication());
}
}
Also, how the graph gets created:
MinimalBible.java
public class MinimalBible extends Application {
private ObjectGraph graph;
private static MinimalBible instance;
public MinimalBible() {
instance = this;
}
#Override
public void onCreate() {
graph = ObjectGraph.create(new MinimalBibleModules());
graph.inject(this);
}
There are two parts here. First, how you get access to the Context. You can do static things as you are, though that's not advisable. Generally, you should configure your graph with a stateful module that carries the context, like this:
#Module
class ApplicationModule {
private final Application application;
public ApplicationModule(Application app) {
this.application = app;
}
// you can mark this singleton, but it's minor overhead
// and the fact that you have a single instance stored
// means it's semantically equivalent. But for clarity
// it's sometimes good to make the point.
#Provides
#Singleton
Application application() {
return application;
}
// optionally: bind it as a Context with a qualifier.
// note: never bind Context without a qualifier annotation
// as Activity and Application are both Context subtypes.
#Provides
#Singleton
#PerApplication
Context appContext(Application app) {
// Doing this instead of returning this.application is
// semantically equivalent but links #PerApplication Context
// to Application, so in graph analysis and error reporting
// the link is clearer. That's a personal choice.
return app;
}
}
At any rate, you then when you create the graph:
Application appInstance = ...;
ObjectGraph appGraph = ObjectGraph.create(
MyAppModule.class,
new ApplicationModule(appInstance));
The Application is then seeded into the graph and can be depended-upon by other types that declare it as a dependency.

Categories

Resources