You've probably finished your course by now, but in case you are still searching, or someone else is: rolling your own DI is actually very simple on Android once you know how to do it.
Creating the Dependency Graph
I usually first start with a custom Application class (don't forget to register it in the android manifest), this class will live throughout the lifecycle of your android app and is where your code can access its dependencies from. Something like this:
public class CustomApp extends Application {
private static ObjectGraph objectGraph;
@Override
public void onCreate() {
super.onCreate();
objectGraph = new ObjectGraph(this);
}
// This is where your code accesses its dependencies
public static <T> T get(Class<T> s) {
Affirm.notNull(objectGraph);
return objectGraph.get(s);
}
// This is how you inject mock dependencies when running tests
public <T> void injectMockObject(Class<T> clazz, T object) {
objectGraph.putMock(clazz, object);
}
}
(Affirm.notNull() just blows up if something is null, you don't need to use it). The actual dependencies are all in the ObjectGraph class which basically looks like this:
class ObjectGraph {
private final Map<Class<?>, Object> dependencies = new HashMap<>();
public ObjectGraph(Application application) {
// Step 1. create dependency graph
AndroidLogger logger = new AndroidLogger();
Wallet wallet = new Wallet(logger);
//... this list can get very long
// Step 2. add models to a dependencies map if you will need them later
dependencies.put(Wallet.class, wallet);
}
<T> T get(Class<T> model) {
Affirm.notNull(model);
T t = model.cast(dependencies.get(model));
Affirm.notNull(t);
return t;
}
<T> void putMock(Class<T> clazz, T object) {
Affirm.notNull(clazz);
Affirm.notNull(object);
dependencies.put(clazz, object);
}
}
Usage
Now wherever you are in your app (in an activity for example) you can inject your dependencies like this:
private Wallet wallet;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
wallet = CustomApp.get(Wallet.class);
//...
}
You can use Dagger in a few different ways, but the nearest Dagger2 equivalent would be something like this:
AppComponent appComponent = CustomApp.getAppComponent();
Wallet wallet = appComponent.getWallet();
Scoped dependencies
What you would be injecting here would be a class that has application level scope. If you just want a local scope object that only exists for as long as you keep a reference to it in your view or activity, you do exactly the same thing but what you inject is a factory class:
In ObjectGraph:
WalletFactory walletFactory = new WalletFactory(logger);
In your Fragment for example:
Wallet wallet = CustomApp.get(WalletFactory.class).getNewWallet();
Testing
All this is done so that you can test your view layer code easily. For example if you want to run an espresso test, you create the application, but before showing the activity, you replace the wallet instance with a mock:
CustomApp.injectMockObject(Wallet.class, mockedWalletWith100Dollars);
This is the wallet instance that will then be picked up by the rest of your code during the test.
In some ways this style of DI is not as flexible as Dagger2, but I think it is a lot clearer - just no need to make DI complicated it's actually quite a basic thing. This style also often results in less boiler plate than using a DI framework (once you include component and module classes).
Full examples
I wrote something similar for 5 sample apps as part of a framework I published (wanting to keep the samples as widely accessible as possible - not everyone likes Dagger). You can see the full examples here: https://github.com/erdo/asaf-project/blob/master/example01databinding/src/main/java/foo/bar/example/asafdatabinding/ObjectGraph.java