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