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