diff options
Diffstat (limited to 'packages/SystemUI/docs/dagger.md')
-rw-r--r-- | packages/SystemUI/docs/dagger.md | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md new file mode 100644 index 000000000000..f81e8cce1f91 --- /dev/null +++ b/packages/SystemUI/docs/dagger.md @@ -0,0 +1,204 @@ +# Dagger 2 in SystemUI +*Dagger 2 is a dependency injection framework that compiles annotations to code +to create dependencies without reflection* + +## Recommended reading + +Go read about Dagger 2. + +TODO: Add some links. + +## State of the world + +Dagger 2 has been turned on for SystemUI and a early first pass has been taken +for converting everything in Dependency.java to use Dagger. Since a lot of +SystemUI depends on Dependency, stubs have been added to Dependency to proxy +any gets through to the instances provided by dagger, this will allow migration +of SystemUI through a number of CLs. + +### How it works in SystemUI + +For the classes that we're using in Dependency and are switching to dagger, the +equivalent dagger version is using @Singleton and only having one instance. +To have the single instance span all of SystemUI and be easily accessible for +other components, there is a single root Component that exists that generates +these. The component lives in SystemUIFactory and is called SystemUIRootComponent. + +```java +@Singleton +@Component(modules = {SystemUIFactory.class, DependencyProvider.class, ContextHolder.class}) +public interface SystemUIRootComponent { + @Singleton + Dependency.DependencyInjector createDependency(); +} +``` + +The root modules are what provides the global singleton dependencies across +SystemUI. ContextHolder is just a wrapper that provides a context. +SystemUIFactory @Provide dependencies that need to be overridden by SystemUI +variants (like other form factors). DependencyProvider provides or binds any +remaining depedencies required. + +### Adding injection to a new SystemUI object + +Anything that depends on any @Singleton provider from SystemUIRootComponent +should be declared as a Subcomponent of the root component, this requires +declaring your own interface for generating your own modules or just the +object you need injected. The subcomponent also needs to be added to +SystemUIRootComponent in SystemUIFactory so it can be acquired. + +```java +public interface SystemUIRootComponent { ++ @Singleton ++ Dependency.DependencyInjector createDependency(); +} + +public class Dependency extends SystemUI { + ... ++ @Subcomponent ++ public interface DependencyInjector { ++ Dependency createSystemUI(); ++ } +} +``` + +For objects that extend SystemUI and require injection, you can define an +injector that creates the injected object for you. This other class should +be referenced in @string/config_systemUIServiceComponents. + +```java +public static class DependencyCreator implements Injector { + @Override + public SystemUI apply(Context context) { + return SystemUIFactory.getInstance().getRootComponent() + .createDependency() + .createSystemUI(); + } +} +``` + +### Adding a new injectable object + +First tag the constructor with @Inject. Also tag it with @Singleton if only one +instance should be created. + +```java +@Singleton +public class SomethingController { + @Inject + public SomethingController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + // context and mainHandler will be automatically populated. + } +} +``` + +If you have an interface class and an implementation class, dagger needs to know +how to map it. The simplest way to do this is to add a provides method to +DependencyProvider. + +```java +public class DependencyProvider { + ... + @Singleton + @Provide + public SomethingController provideSomethingController(Context context, + @Named(MAIN_HANDLER_NAME) Handler mainHandler) { + return new SomethingControllerImpl(context, mainHandler); + } +} +``` + +If you need to access this from Dependency#get, then add an adapter to Dependency +that maps to the instance provided by Dagger. The changes should be similar +to the following diff. + +```java +public class Dependency { + ... + @Inject Lazy<SomethingController> mSomethingController; + ... + public void start() { + ... + mProviders.put(SomethingController.class, mSomethingController::get); + } +} +``` + +### Using injection with Fragments + +Fragments are created as part of the FragmentManager, so they need to be +setup so the manager knows how to create them. To do that, add a method +to com.android.systemui.fragments.FragmentService$FragmentCreator that +returns your fragment class. Thats all thats required, once the method +exists, FragmentService will automatically pick it up and use injection +whenever your fragment needs to be created. + +```java +public interface FragmentCreator { ++ NavigationBarFragment createNavigationBar(); +} +``` + +If you need to create your fragment (i.e. for the add or replace transaction), +then the FragmentHostManager can do this for you. + +```java +FragmentHostManager.get(view).create(NavigationBarFragment.class); +``` + +### Using injection with Views + +Generally, you shouldn't need to inject for a view, as the view should +be relatively self contained and logic that requires injection should be +moved to a higher level construct such as a Fragment or a top-level SystemUI +component, see above for how to do injection for both of which. + +Still here? Yeah, ok, sysui has a lot of pre-existing views that contain a +lot of code that could benefit from injection and will need to be migrated +off from Dependency#get uses. Similar to how fragments are injected, the view +needs to be added to the interface +com.android.systemui.util.InjectionInflationController$ViewInstanceCreator. + +```java +public interface ViewInstanceCreator { ++ QuickStatusBarHeader createQsHeader(); +} +``` + +Presumably you need to inflate that view from XML (otherwise why do you +need anything special? see earlier sections about generic injection). To obtain +an inflater that supports injected objects, call InjectionInflationController#injectable, +which will wrap the inflater it is passed in one that can create injected +objects when needed. + +```java +@Override +public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + Bundle savedInstanceState) { + return mInjectionInflater.injectable(inflater).inflate(R.layout.my_layout, container, false); +} +``` + +There is one other important thing to note about injecting with views. SysUI +already has a Context in its global dagger component, so if you simply inject +a Context, you will not get the one that the view should have with proper +theming. Because of this, always ensure to tag views that have @Inject with +the @Named view context. + +```java +public CustomView(@Named(VIEW_CONTEXT) Context themedViewContext, AttributeSet attrs, + OtherCustomDependency something) { + ... +} +``` + +## TODO List + + - Eliminate usages of Depndency#get + - Add support for Fragments to handle injection automatically + - (this could be through dagger2-android or something custom) + - Reduce number of things with @Provide in DependencyProvider (many can be + @Inject instead) + - Migrate as many remaining DependencyProvider instances to @Bind + - Add links in above TODO |