blob: f81e8cce1f9162fba45e4ffdf2e36d4bc55fdfbe [file] [log] [blame] [view]
Jason Monk27d01a622018-12-10 15:57:09 -05001# Dagger 2 in SystemUI
2*Dagger 2 is a dependency injection framework that compiles annotations to code
3to create dependencies without reflection*
4
5## Recommended reading
6
7Go read about Dagger 2.
8
9TODO: Add some links.
10
11## State of the world
12
13Dagger 2 has been turned on for SystemUI and a early first pass has been taken
14for converting everything in Dependency.java to use Dagger. Since a lot of
15SystemUI depends on Dependency, stubs have been added to Dependency to proxy
16any gets through to the instances provided by dagger, this will allow migration
17of SystemUI through a number of CLs.
18
19### How it works in SystemUI
20
21For the classes that we're using in Dependency and are switching to dagger, the
22equivalent dagger version is using @Singleton and only having one instance.
23To have the single instance span all of SystemUI and be easily accessible for
24other components, there is a single root Component that exists that generates
25these. The component lives in SystemUIFactory and is called SystemUIRootComponent.
26
27```java
28@Singleton
29@Component(modules = {SystemUIFactory.class, DependencyProvider.class, ContextHolder.class})
30public interface SystemUIRootComponent {
31 @Singleton
32 Dependency.DependencyInjector createDependency();
33}
34```
35
36The root modules are what provides the global singleton dependencies across
37SystemUI. ContextHolder is just a wrapper that provides a context.
38SystemUIFactory @Provide dependencies that need to be overridden by SystemUI
39variants (like other form factors). DependencyProvider provides or binds any
40remaining depedencies required.
41
42### Adding injection to a new SystemUI object
43
44Anything that depends on any @Singleton provider from SystemUIRootComponent
45should be declared as a Subcomponent of the root component, this requires
46declaring your own interface for generating your own modules or just the
47object you need injected. The subcomponent also needs to be added to
48SystemUIRootComponent in SystemUIFactory so it can be acquired.
49
50```java
51public interface SystemUIRootComponent {
52+ @Singleton
53+ Dependency.DependencyInjector createDependency();
54}
55
56public class Dependency extends SystemUI {
57 ...
58+ @Subcomponent
59+ public interface DependencyInjector {
60+ Dependency createSystemUI();
61+ }
62}
63```
64
65For objects that extend SystemUI and require injection, you can define an
66injector that creates the injected object for you. This other class should
67be referenced in @string/config_systemUIServiceComponents.
68
69```java
70public static class DependencyCreator implements Injector {
71 @Override
72 public SystemUI apply(Context context) {
73 return SystemUIFactory.getInstance().getRootComponent()
74 .createDependency()
75 .createSystemUI();
76 }
77}
78```
79
80### Adding a new injectable object
81
82First tag the constructor with @Inject. Also tag it with @Singleton if only one
83instance should be created.
84
85```java
86@Singleton
87public class SomethingController {
88 @Inject
89 public SomethingController(Context context,
90 @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
91 // context and mainHandler will be automatically populated.
92 }
93}
94```
95
96If you have an interface class and an implementation class, dagger needs to know
97how to map it. The simplest way to do this is to add a provides method to
98DependencyProvider.
99
100```java
101public class DependencyProvider {
102 ...
103 @Singleton
104 @Provide
105 public SomethingController provideSomethingController(Context context,
106 @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
107 return new SomethingControllerImpl(context, mainHandler);
108 }
109}
110```
111
112If you need to access this from Dependency#get, then add an adapter to Dependency
113that maps to the instance provided by Dagger. The changes should be similar
114to the following diff.
115
116```java
117public class Dependency {
118 ...
119 @Inject Lazy<SomethingController> mSomethingController;
120 ...
121 public void start() {
122 ...
123 mProviders.put(SomethingController.class, mSomethingController::get);
124 }
125}
126```
127
Jason Monk9424af72018-12-19 14:17:26 -0500128### Using injection with Fragments
129
130Fragments are created as part of the FragmentManager, so they need to be
131setup so the manager knows how to create them. To do that, add a method
132to com.android.systemui.fragments.FragmentService$FragmentCreator that
133returns your fragment class. Thats all thats required, once the method
134exists, FragmentService will automatically pick it up and use injection
135whenever your fragment needs to be created.
136
137```java
138public interface FragmentCreator {
139+ NavigationBarFragment createNavigationBar();
140}
141```
142
143If you need to create your fragment (i.e. for the add or replace transaction),
144then the FragmentHostManager can do this for you.
145
146```java
147FragmentHostManager.get(view).create(NavigationBarFragment.class);
148```
149
Jason Monkea54e8a2018-12-20 10:01:48 -0500150### Using injection with Views
151
152Generally, you shouldn't need to inject for a view, as the view should
153be relatively self contained and logic that requires injection should be
154moved to a higher level construct such as a Fragment or a top-level SystemUI
155component, see above for how to do injection for both of which.
156
157Still here? Yeah, ok, sysui has a lot of pre-existing views that contain a
158lot of code that could benefit from injection and will need to be migrated
159off from Dependency#get uses. Similar to how fragments are injected, the view
160needs to be added to the interface
161com.android.systemui.util.InjectionInflationController$ViewInstanceCreator.
162
163```java
164public interface ViewInstanceCreator {
165+ QuickStatusBarHeader createQsHeader();
166}
167```
168
169Presumably you need to inflate that view from XML (otherwise why do you
170need anything special? see earlier sections about generic injection). To obtain
171an inflater that supports injected objects, call InjectionInflationController#injectable,
172which will wrap the inflater it is passed in one that can create injected
173objects when needed.
174
175```java
176@Override
177public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
178 Bundle savedInstanceState) {
179 return mInjectionInflater.injectable(inflater).inflate(R.layout.my_layout, container, false);
180}
181```
182
183There is one other important thing to note about injecting with views. SysUI
184already has a Context in its global dagger component, so if you simply inject
185a Context, you will not get the one that the view should have with proper
186theming. Because of this, always ensure to tag views that have @Inject with
187the @Named view context.
188
189```java
190public CustomView(@Named(VIEW_CONTEXT) Context themedViewContext, AttributeSet attrs,
191 OtherCustomDependency something) {
192 ...
193}
194```
195
Jason Monk27d01a622018-12-10 15:57:09 -0500196## TODO List
197
198 - Eliminate usages of Depndency#get
199 - Add support for Fragments to handle injection automatically
200 - (this could be through dagger2-android or something custom)
201 - Reduce number of things with @Provide in DependencyProvider (many can be
202 @Inject instead)
203 - Migrate as many remaining DependencyProvider instances to @Bind
204 - Add links in above TODO