summaryrefslogtreecommitdiff
path: root/packages/SystemUI/docs/dagger.md
blob: c440fba10135b347aa13fc453e76f530462596fc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# 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.

 - [User's guide](https://google.github.io/dagger/users-guide)

TODO: Add some links.

## State of the world

Dagger 2 has been turned on for SystemUI and a early first pass has been taken
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.

### How it works in SystemUI

For the classes that we're using in Dependency and are switching to dagger, the
equivalent dagger version is using `@Singleton` and therefore only has one instance.
To have the single instance span all of SystemUI and be easily accessible for
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`.

```java

@Singleton
@Component(modules = {SystemUIFactory.class, DependencyProvider.class, DependencyBinder.class,
        ContextHolder.class})
public interface SystemUIRootComponent {
    @Singleton
    Dependency.DependencyInjector createDependency();
}
```

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.

### Adding injection to a new SystemUI object

Anything that depends on any `@Singleton` provider from SystemUIRootComponent
should be declared as a `@Subcomponent` of the root component. This requires
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 {
  //...
+  @Subcomponent
+  public interface DependencyInjector {
+      Dependency createSystemUI();
+  }
}
```

For objects which extend SystemUI and require injection, you can define an
injector that creates the injected object for you. This other class should
be referenced in [@string/config_systemUIServiceComponents](packages/SystemUI/res/values/config.xml).

```java
public static class DependencyCreator implements Injector {
    @Override
    public SystemUI apply(Context context) {
        return SystemUIFactory.getInstance().getRootComponent()
                .createDependency()
                .createSystemUI();
    }
}
```

### Adding a new injectable object

First tag the constructor with `@Inject`. Also tag it with `@Singleton` if only one
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
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.

```java
public class DependencyProvider {
  //...
  @Singleton
  @Provides
  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 {
  //...
  @Inject Lazy<SomethingController> mSomethingController;
  //...
  public void start() {
    //...
    mProviders.put(SomethingController.class, mSomethingController::get);
  }
}
```

### 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);
```

### 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) {
    //...
}
```

## 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/)


## TODO List

 - Eliminate usages of Dependency#get
 - Add links in above TODO