2018-12-10 15:57:09 -05:00
|
|
|
# Dagger 2 in SystemUI
|
|
|
|
*Dagger 2 is a dependency injection framework that compiles annotations to code
|
|
|
|
to create dependencies without reflection*
|
|
|
|
|
|
|
|
## Recommended reading
|
|
|
|
|
|
|
|
Go read about Dagger 2.
|
|
|
|
|
2018-12-27 07:32:10 -05:00
|
|
|
- [User's guide](https://google.github.io/dagger/users-guide)
|
|
|
|
|
2018-12-10 15:57:09 -05:00
|
|
|
TODO: Add some links.
|
|
|
|
|
|
|
|
## State of the world
|
|
|
|
|
|
|
|
Dagger 2 has been turned on for SystemUI and a early first pass has been taken
|
2019-03-06 16:40:18 -05:00
|
|
|
for converting everything in [Dependency.java](packages/systemui/src/com/android/systemui/Dependency.java)
|
|
|
|
to use Dagger. Since a lot of SystemUI depends on Dependency, stubs have been added to Dependency
|
|
|
|
to proxy any gets through to the instances provided by dagger, this will allow migration of SystemUI
|
|
|
|
through a number of CLs.
|
2018-12-10 15:57:09 -05:00
|
|
|
|
|
|
|
### How it works in SystemUI
|
|
|
|
|
|
|
|
For the classes that we're using in Dependency and are switching to dagger, the
|
2019-03-06 16:40:18 -05:00
|
|
|
equivalent dagger version is using `@Singleton` and therefore only has one instance.
|
2018-12-10 15:57:09 -05:00
|
|
|
To have the single instance span all of SystemUI and be easily accessible for
|
2019-03-06 16:40:18 -05:00
|
|
|
other components, there is a single root `@Component` that exists that generates
|
|
|
|
these. The component lives in [SystemUIFactory](packages/systemui/src/com/android/systemui/SystemUIFactory.java)
|
|
|
|
and is called `SystemUIRootComponent`.
|
2018-12-10 15:57:09 -05:00
|
|
|
|
|
|
|
```java
|
2019-03-06 16:40:18 -05:00
|
|
|
|
2018-12-10 15:57:09 -05:00
|
|
|
@Singleton
|
2019-03-06 16:40:18 -05:00
|
|
|
@Component(modules = {SystemUIFactory.class, DependencyProvider.class, DependencyBinder.class,
|
|
|
|
ContextHolder.class})
|
2018-12-10 15:57:09 -05:00
|
|
|
public interface SystemUIRootComponent {
|
|
|
|
@Singleton
|
|
|
|
Dependency.DependencyInjector createDependency();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-03-06 16:40:18 -05:00
|
|
|
The root component is composed of root modules, which in turn provide the global singleton
|
|
|
|
dependencies across all of SystemUI.
|
|
|
|
|
|
|
|
- `ContextHolder` is just a wrapper that provides a context.
|
|
|
|
|
|
|
|
- `SystemUIFactory` `@Provides` dependencies that need to be overridden by SystemUI
|
|
|
|
variants (like other form factors e.g. Car).
|
|
|
|
|
|
|
|
- `DependencyBinder` creates the mapping from interfaces to implementation classes.
|
|
|
|
|
|
|
|
- `DependencyProvider` provides or binds any remaining depedencies required.
|
2018-12-10 15:57:09 -05:00
|
|
|
|
|
|
|
### Adding injection to a new SystemUI object
|
|
|
|
|
2019-03-06 16:40:18 -05:00
|
|
|
Anything that depends on any `@Singleton` provider from SystemUIRootComponent
|
2019-07-03 13:26:55 -04:00
|
|
|
should be declared as a `@Subcomponent` of the root component. This requires
|
2018-12-10 15:57:09 -05:00
|
|
|
declaring your own interface for generating your own modules or just the
|
|
|
|
object you need injected. The subcomponent also needs to be added to
|
|
|
|
SystemUIRootComponent in SystemUIFactory so it can be acquired.
|
|
|
|
|
|
|
|
```java
|
|
|
|
public interface SystemUIRootComponent {
|
|
|
|
+ @Singleton
|
|
|
|
+ Dependency.DependencyInjector createDependency();
|
|
|
|
}
|
|
|
|
|
|
|
|
public class Dependency extends SystemUI {
|
2019-03-06 16:40:18 -05:00
|
|
|
//...
|
2018-12-10 15:57:09 -05:00
|
|
|
+ @Subcomponent
|
|
|
|
+ public interface DependencyInjector {
|
|
|
|
+ Dependency createSystemUI();
|
|
|
|
+ }
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-03-06 16:40:18 -05:00
|
|
|
For objects which extend SystemUI and require injection, you can define an
|
2018-12-10 15:57:09 -05:00
|
|
|
injector that creates the injected object for you. This other class should
|
2019-03-06 16:40:18 -05:00
|
|
|
be referenced in [@string/config_systemUIServiceComponents](packages/SystemUI/res/values/config.xml).
|
2018-12-10 15:57:09 -05:00
|
|
|
|
|
|
|
```java
|
|
|
|
public static class DependencyCreator implements Injector {
|
|
|
|
@Override
|
|
|
|
public SystemUI apply(Context context) {
|
|
|
|
return SystemUIFactory.getInstance().getRootComponent()
|
|
|
|
.createDependency()
|
|
|
|
.createSystemUI();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Adding a new injectable object
|
|
|
|
|
2019-03-06 16:40:18 -05:00
|
|
|
First tag the constructor with `@Inject`. Also tag it with `@Singleton` if only one
|
2018-12-10 15:57:09 -05:00
|
|
|
instance should be created.
|
|
|
|
|
|
|
|
```java
|
|
|
|
@Singleton
|
|
|
|
public class SomethingController {
|
|
|
|
@Inject
|
|
|
|
public SomethingController(Context context,
|
|
|
|
@Named(MAIN_HANDLER_NAME) Handler mainHandler) {
|
|
|
|
// context and mainHandler will be automatically populated.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If you have an interface class and an implementation class, dagger needs to know
|
2019-03-06 16:40:18 -05:00
|
|
|
how to map it. The simplest way to do this is to add an `@Provides` method to
|
|
|
|
DependencyProvider. The type of the return value tells dagger which dependency it's providing.
|
2018-12-10 15:57:09 -05:00
|
|
|
|
|
|
|
```java
|
|
|
|
public class DependencyProvider {
|
2019-03-06 16:40:18 -05:00
|
|
|
//...
|
2018-12-10 15:57:09 -05:00
|
|
|
@Singleton
|
2019-03-06 16:40:18 -05:00
|
|
|
@Provides
|
2018-12-10 15:57:09 -05:00
|
|
|
public SomethingController provideSomethingController(Context context,
|
|
|
|
@Named(MAIN_HANDLER_NAME) Handler mainHandler) {
|
|
|
|
return new SomethingControllerImpl(context, mainHandler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If you need to access this from Dependency#get, then add an adapter to Dependency
|
|
|
|
that maps to the instance provided by Dagger. The changes should be similar
|
|
|
|
to the following diff.
|
|
|
|
|
|
|
|
```java
|
|
|
|
public class Dependency {
|
2019-03-06 16:40:18 -05:00
|
|
|
//...
|
2018-12-10 15:57:09 -05:00
|
|
|
@Inject Lazy<SomethingController> mSomethingController;
|
2019-03-06 16:40:18 -05:00
|
|
|
//...
|
2018-12-10 15:57:09 -05:00
|
|
|
public void start() {
|
2019-03-06 16:40:18 -05:00
|
|
|
//...
|
2018-12-10 15:57:09 -05:00
|
|
|
mProviders.put(SomethingController.class, mSomethingController::get);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2018-12-19 14:17:26 -05:00
|
|
|
### Using injection with Fragments
|
|
|
|
|
|
|
|
Fragments are created as part of the FragmentManager, so they need to be
|
|
|
|
setup so the manager knows how to create them. To do that, add a method
|
|
|
|
to com.android.systemui.fragments.FragmentService$FragmentCreator that
|
|
|
|
returns your fragment class. Thats all thats required, once the method
|
|
|
|
exists, FragmentService will automatically pick it up and use injection
|
|
|
|
whenever your fragment needs to be created.
|
|
|
|
|
|
|
|
```java
|
|
|
|
public interface FragmentCreator {
|
|
|
|
+ NavigationBarFragment createNavigationBar();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
If you need to create your fragment (i.e. for the add or replace transaction),
|
|
|
|
then the FragmentHostManager can do this for you.
|
|
|
|
|
|
|
|
```java
|
|
|
|
FragmentHostManager.get(view).create(NavigationBarFragment.class);
|
|
|
|
```
|
|
|
|
|
2018-12-20 10:01:48 -05:00
|
|
|
### Using injection with Views
|
|
|
|
|
|
|
|
Generally, you shouldn't need to inject for a view, as the view should
|
|
|
|
be relatively self contained and logic that requires injection should be
|
|
|
|
moved to a higher level construct such as a Fragment or a top-level SystemUI
|
|
|
|
component, see above for how to do injection for both of which.
|
|
|
|
|
|
|
|
Still here? Yeah, ok, sysui has a lot of pre-existing views that contain a
|
|
|
|
lot of code that could benefit from injection and will need to be migrated
|
|
|
|
off from Dependency#get uses. Similar to how fragments are injected, the view
|
|
|
|
needs to be added to the interface
|
|
|
|
com.android.systemui.util.InjectionInflationController$ViewInstanceCreator.
|
|
|
|
|
|
|
|
```java
|
|
|
|
public interface ViewInstanceCreator {
|
|
|
|
+ QuickStatusBarHeader createQsHeader();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Presumably you need to inflate that view from XML (otherwise why do you
|
|
|
|
need anything special? see earlier sections about generic injection). To obtain
|
|
|
|
an inflater that supports injected objects, call InjectionInflationController#injectable,
|
|
|
|
which will wrap the inflater it is passed in one that can create injected
|
|
|
|
objects when needed.
|
|
|
|
|
|
|
|
```java
|
|
|
|
@Override
|
|
|
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
|
|
|
Bundle savedInstanceState) {
|
|
|
|
return mInjectionInflater.injectable(inflater).inflate(R.layout.my_layout, container, false);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
There is one other important thing to note about injecting with views. SysUI
|
|
|
|
already has a Context in its global dagger component, so if you simply inject
|
|
|
|
a Context, you will not get the one that the view should have with proper
|
|
|
|
theming. Because of this, always ensure to tag views that have @Inject with
|
|
|
|
the @Named view context.
|
|
|
|
|
|
|
|
```java
|
|
|
|
public CustomView(@Named(VIEW_CONTEXT) Context themedViewContext, AttributeSet attrs,
|
|
|
|
OtherCustomDependency something) {
|
2019-03-06 16:40:18 -05:00
|
|
|
//...
|
2018-12-20 10:01:48 -05:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-07-03 13:26:55 -04:00
|
|
|
## Updating Dagger2
|
|
|
|
|
|
|
|
Binaries can be downloaded from https://repo1.maven.org/maven2/com/google/dagger/ and then loaded
|
|
|
|
into
|
|
|
|
[/prebuilts/tools/common/m2/repository/com/google/dagger/](http://cs/android/prebuilts/tools/common/m2/repository/com/google/dagger/)
|
|
|
|
|
|
|
|
|
2018-12-10 15:57:09 -05:00
|
|
|
## TODO List
|
|
|
|
|
2018-12-20 13:25:34 -05:00
|
|
|
- Eliminate usages of Dependency#get
|
2018-12-10 15:57:09 -05:00
|
|
|
- Add links in above TODO
|