Home | History | Annotate | Download | only in docs
      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