blob: 7751bb10c5ce030a2914e727787fc1b4db41e9a0 [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
Jason Monk196d6392018-12-20 13:25:34 -050039variants (like other form factors). DependencyBinder creates the mapping from
40interfaces to implementation classes. DependencyProvider provides or binds any
Jason Monk27d01a622018-12-10 15:57:09 -050041remaining depedencies required.
42
43### Adding injection to a new SystemUI object
44
45Anything that depends on any @Singleton provider from SystemUIRootComponent
46should be declared as a Subcomponent of the root component, this requires
47declaring your own interface for generating your own modules or just the
48object you need injected. The subcomponent also needs to be added to
49SystemUIRootComponent in SystemUIFactory so it can be acquired.
50
51```java
52public interface SystemUIRootComponent {
53+ @Singleton
54+ Dependency.DependencyInjector createDependency();
55}
56
57public class Dependency extends SystemUI {
58 ...
59+ @Subcomponent
60+ public interface DependencyInjector {
61+ Dependency createSystemUI();
62+ }
63}
64```
65
66For objects that extend SystemUI and require injection, you can define an
67injector that creates the injected object for you. This other class should
68be referenced in @string/config_systemUIServiceComponents.
69
70```java
71public static class DependencyCreator implements Injector {
72 @Override
73 public SystemUI apply(Context context) {
74 return SystemUIFactory.getInstance().getRootComponent()
75 .createDependency()
76 .createSystemUI();
77 }
78}
79```
80
81### Adding a new injectable object
82
83First tag the constructor with @Inject. Also tag it with @Singleton if only one
84instance should be created.
85
86```java
87@Singleton
88public class SomethingController {
89 @Inject
90 public SomethingController(Context context,
91 @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
92 // context and mainHandler will be automatically populated.
93 }
94}
95```
96
97If you have an interface class and an implementation class, dagger needs to know
98how to map it. The simplest way to do this is to add a provides method to
99DependencyProvider.
100
101```java
102public class DependencyProvider {
103 ...
104 @Singleton
105 @Provide
106 public SomethingController provideSomethingController(Context context,
107 @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
108 return new SomethingControllerImpl(context, mainHandler);
109 }
110}
111```
112
113If you need to access this from Dependency#get, then add an adapter to Dependency
114that maps to the instance provided by Dagger. The changes should be similar
115to the following diff.
116
117```java
118public class Dependency {
119 ...
120 @Inject Lazy<SomethingController> mSomethingController;
121 ...
122 public void start() {
123 ...
124 mProviders.put(SomethingController.class, mSomethingController::get);
125 }
126}
127```
128
Jason Monk9424af72018-12-19 14:17:26 -0500129### Using injection with Fragments
130
131Fragments are created as part of the FragmentManager, so they need to be
132setup so the manager knows how to create them. To do that, add a method
133to com.android.systemui.fragments.FragmentService$FragmentCreator that
134returns your fragment class. Thats all thats required, once the method
135exists, FragmentService will automatically pick it up and use injection
136whenever your fragment needs to be created.
137
138```java
139public interface FragmentCreator {
140+ NavigationBarFragment createNavigationBar();
141}
142```
143
144If you need to create your fragment (i.e. for the add or replace transaction),
145then the FragmentHostManager can do this for you.
146
147```java
148FragmentHostManager.get(view).create(NavigationBarFragment.class);
149```
150
Jason Monkea54e8a2018-12-20 10:01:48 -0500151### Using injection with Views
152
153Generally, you shouldn't need to inject for a view, as the view should
154be relatively self contained and logic that requires injection should be
155moved to a higher level construct such as a Fragment or a top-level SystemUI
156component, see above for how to do injection for both of which.
157
158Still here? Yeah, ok, sysui has a lot of pre-existing views that contain a
159lot of code that could benefit from injection and will need to be migrated
160off from Dependency#get uses. Similar to how fragments are injected, the view
161needs to be added to the interface
162com.android.systemui.util.InjectionInflationController$ViewInstanceCreator.
163
164```java
165public interface ViewInstanceCreator {
166+ QuickStatusBarHeader createQsHeader();
167}
168```
169
170Presumably you need to inflate that view from XML (otherwise why do you
171need anything special? see earlier sections about generic injection). To obtain
172an inflater that supports injected objects, call InjectionInflationController#injectable,
173which will wrap the inflater it is passed in one that can create injected
174objects when needed.
175
176```java
177@Override
178public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
179 Bundle savedInstanceState) {
180 return mInjectionInflater.injectable(inflater).inflate(R.layout.my_layout, container, false);
181}
182```
183
184There is one other important thing to note about injecting with views. SysUI
185already has a Context in its global dagger component, so if you simply inject
186a Context, you will not get the one that the view should have with proper
187theming. Because of this, always ensure to tag views that have @Inject with
188the @Named view context.
189
190```java
191public CustomView(@Named(VIEW_CONTEXT) Context themedViewContext, AttributeSet attrs,
192 OtherCustomDependency something) {
193 ...
194}
195```
196
Jason Monk27d01a622018-12-10 15:57:09 -0500197## TODO List
198
Jason Monk196d6392018-12-20 13:25:34 -0500199 - Eliminate usages of Dependency#get
Jason Monk27d01a622018-12-10 15:57:09 -0500200 - Add links in above TODO