Ámbitos (Scopes)
Todos los componentes tienen un ámbito. El ámbito viene determinado por cuándo creamos y cuándo eliminamos la instancia del componente. Normalmente, el componente se instancia en la clase aplicación, por lo que este tiene ámbito de aplicación. Si creáramos y destruyésemos la instancia del componente en cada activity, sería de ámbito activity. Es decir, que el ámbito es la clase donde se crea y se destruye. Normalmente nos interesará que el componente de un ámbito inferior que el de aplicación permita inyectar todo lo que puede inyectar un componente de ámbito de aplicación y además otros objetos más específicos para su propio ámbito. Esto se consigue con los subcomponentes. Un componente de ámbito superior puede contener subcomponentes de ámbitos inferiores que permiten inyectar todo lo que se declare en sus módulos además de todo lo que pueda inyectar el componente padre. Para ilustrarlo mejor, vamos a suponer el siguiente caso: tenemos la clase ClaseA, la cual queremos inyectar en varios lugares de la aplicación, y también tenemos la clase ClaseB, la cual solo inyectaremos en las activities. Crearemos un componente y un módulo que contenga un método provider para la clase ClaseA y un subcomponente y un módulo que tenga un método provider para la clase ClaseB.Componente y subcomponente
Un subcomponente es igualmente un componente pero se crea con la anotación@Subcomponent
:
1 2 3 4 |
@Subcomponent(module = ModuloSubcomponente.class) public interface Subcomponente{ void inject(MainActivity mainActivity); } |
1 2 3 4 |
@Component(module = ModuloComponente.class) public interface Componente(){ Subcomponente plus(ModuloSubcomponente moduloSubcomponente); } |
plus()
porque parece que se viene haciendo desde Dagger 1. Lo importante es que devuelva un objeto del tipo del subcomponente y que reciba los módulos que lo conforman.
Observa que hemos movido el método inject(MainActivity)
del componente al subcomponente, ya que para las activities queremos utilizar el subcomponente de ámbito activityy no el componente raíz. Si hubiera más activities, crearíamos también los métodos necesarios para inicializar la inyección en estas.
Módulos
El módulo de Componente contendrá un método provider para inyectar instancias de ClaseA y el módulo de Subcomponente tendrá uno para inyectar instancias de ClaseB:
1 2 3 4 5 6 7 8 |
@Module public class ModuloComponente{ @Provides public ClaseA provideClaseA(){ return new ClaseA(); } } |
1 2 3 4 5 6 7 8 |
@Module public class ModuloSubcomponente{ @Provide public ClaseB provideClaseB(){ return new ClaseB(); } } |
Instanciación
Como hemos dicho, lo que verdaderamente determina el ámbito del subcomponente es dónde creas y dónde destruyes su instancia. En este caso, como queremos que el ámbito sea de activity, debemos crear y destruir el subcomponente cada vez que se cree y se destruya una activity para que cada una tenga el suyo. Pero para acceder al subcomponente necesitamos la instancia del componente padre:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class MiAplicacion extends Application { private static Componente mComponente; @Override public void onCreate() { super.onCreate(); crearComponente(); } public void crearComponente() { mComponente = DaggerComponente.builder() .ModuloComponente(new ModuloComponente()) .build(); } public static Componente getComponente() { return mComponente; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class MainActivity extends AppCompatActivity{ private Subcomponente subcomponente; private ClaseA claseA; private ClaseB claseB; protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); subcomponente = MiAplicacion.getComponente().plus(new ModuloSubcomponente()); subcomponente.inject(this); } } |
inject()
de Subcomponente
se inicializarán tanto claseB
como claseA
. Esto es así porque, como ya hemos dicho al principio, un componente de un ámbito inferior puede inyectar todo aquello que puede inyectar su componente padre.
No habríamos podido inicializar la inyección mediante el componente padre utilizando MiAplicacion.getComponente().inject(this)
porque:
- El método
inject()
está en el subcomponente. - Aunque lo moviéramos al componente, el componente no tiene acceso a los providers del módulo del subcomponente, por lo que no podría inyectar
claseB
onCreate()
y, al ser un campo de la activity, será destruido cuando esta se destruya. Pasa lo mismo con el ámbito de mComponente
: su ámbito es de aplicación porque es creado en la clase MiAplicación y será destruido cuando la aplicación se destruya.
Singletons con ámbito
Un componente tiene un solo ámbito. El ámbito@Singleton
es el más grande y está reservado para el componente raíz. Como normalmente lo creamos en la clase de la aplicación, el ámbito de @Singleton
es de aplicación y todas las instancias generadas por un método provider marcado con @Singleton
son únicas en toda la aplicación. Si queremos generar instancias únicas también en los subcomponentes, tenemos que crear otra anotación diferente.
Fíjate en la declaración de la anotación @Singleton
:
1 2 3 |
@Scope @Retention(Retention.RUNTIME) public @interface Singleton{} |
@Scope
es lo que le dice a Dagger que las instancias @Singleton
pertenecen a un ámbito. Así que, para diferenciar entre el ámbito del componente y el del subcomponente, tenemos que crear una anotación nueva marcada también con @Scope
:
1 2 3 |
@Scope @Retention(Retention.RUNTIME) public @interface MiAmbito{} |
@Singleton
está reservada para el componente raíz:
1 2 3 4 5 |
@Singleton @Component(module = ModuloComponente.class) public interface Componente(){ Subcomponente plus(ModuloSubcomponente moduloSubcomponente); } |
1 2 3 4 5 |
@MiAmbito @Subcomponent(module = ModuloSubcomponente.class) public interface Subcomponente{ void inject(MainActivity mainActivity); } |
1 2 3 4 5 6 7 8 9 |
@Module public class ModuloComponente{ @Singleton @Provides public ClaseA provideClaseA(){ return new ClaseA(); } } |
1 2 3 4 5 6 7 8 9 |
@Module public class ModuloSubcomponente{ @MiAmbito @Provide public ClaseB provideClaseB(){ return new ClaseB(); } } |
1 2 3 |
Subcomponente subcomponente1, subcomponete2; subcomponente1 = MiAplicacion.getComponente().plus(new ModuloSubcomponente()); subcomponente2 = MiAplicacion.getComponente().plus(new ModuloSubcomponente()); |
inject()
de subcomponente1
se obtendría siempre la misma instancia, y si llamamos al inject()
de subcomponente2
se obtendría siempre la misma instancia, pero sería distinta de la obtenida con subcomponente1
. Las instancias son únicas por componente.
Y lo mismo sucederá si el componente es reinstanciado: las instancias únicas que genere el nuevo serán distintas de las instancias únicas del anterior.