java - medium - Daga 2: dos métodos que proporcionan la misma interfaz.
dagger android support (4)
Además de @Named
y calificadores personalizados (que se muestran en otras respuestas), también puede usar un calificador personalizado con un parámetro enum
:
// Definition
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface ShapeType {
ShapeTypeEnum value(); /* default ShapeTypeEnum.RECTANGLE; */
}
public enum ShapeTypeEnum {
RECTANGLE, CIRCLE
}
// Usage
@Provides @ShapeType(ShapeTypeEnum.RECTANGLE)
public Shape provideRectangle() {
return new Rectangle();
}
@Inject @ShapeType(ShapeTypeEnum.RECTANGLE) Shape rectangle;
Este es un híbrido entre @Named
(que requiere claves de cadena, que es propensa a errores y no se puede completar automáticamente) y calificadores personalizados (que requieren un archivo para cada implementación).
digamos que tengo:
public interface Shape {}
public class Rectangle implements Shape {
}
public class Circle implements Shape {
}
y tengo un ApplicationModule que necesita proporcionar instancias tanto para Rec como para Circle :
@Module
public class ApplicationModule {
private Shape rec;
private Shape circle;
public ApplicationModule() {
rec = new Rectangle();
circle= new Circle ();
}
@Provides
public Shape provideRectangle() {
return rec ;
}
@Provides
public Shape provideCircle() {
return circle;
}
}
y ApplicationComponent :
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
Shape provideRectangle();
}
Con el código tal como está, no se compilará. error diciendo
Error: (33, 20) error: La forma se enlaza varias veces.
Para mí tiene sentido que esto no se pueda hacer, porque el componente está tratando de encontrar una instancia de Shape
, y encuentra dos de ellas, por lo que no sabe cuál devolver.
Mi pregunta es: ¿cómo puedo manejar este problema?
No creo que sea una buena idea utilizar el new
operador dentro del constructor del Module
. Esto crearía una instancia de cada uno de sus objetos provistos al inicializar su gráfico de objeto (es decir, cuando llama a un new ApplicationModule()
) en lugar de cuando Dagger necesita el objeto por primera vez. En este caso (con solo dos objetos), sería despreciable, pero en proyectos más grandes esto podría causar un cuello de botella al inicio de la aplicación. En su lugar, seguiría la sugerencia de @ sector11 y crearía una instancia de sus objetos en los métodos anotados @Provides
.
En cuanto a proporcionar dos objetos del mismo tipo, tanto @Jeff como @Amir son correctos. Puede usar el calificador @Named()
proporcionado, o crear sus propios calificadores, de este modo:
@Qualifier @Retention(RetentionPolicy.RUNTIME)
public @interface RectangleShape {}
@Qualifier @Retention(RetentionPolicy.RUNTIME)
public @interface CircleShape {}
Entonces tu ApplicationModule
debería verse así:
@Module
public class ApplicationModule {
@Provides @RectangleShape // @Named("rectangle")
public Shape provideRectangle() {
return new Rectangle();
}
@Provides @CircleShape // @Named("circle")
public Shape provideCircle() {
return new Circle();
}
}
Con esto puedes inyectar estos objetos en tus clases de esta manera:
@Inject @RectangleShape /* @Named("rectangle") */ public Shape mRectangle;
@Inject @CircleShape /* @Named("circle") */ public Shape mCircle;
Si necesita proporcionar las instancias de sus clases de Shape
sin una anotación @Inject
, puede hacerlo en su clase Component
:
@Component(modules = { ApplicationModule.class })
public interface ApplicationComponent {
void inject(MyApplication application);
@RectangleShape // @Named("rectangle")
Shape getRectangle();
@CircleShape // @Named("circle")
Shape getCircle();
}
Estos métodos proporcionarán la misma instancia de cada clase proporcionada por los métodos anotados de @Provides
.
Recientemente publiqué la respuesta a una pregunta como esta en esta publicación:
Dagger 2: error al obtener varias instancias del mismo objeto con @Named
Necesitas usar @Named("someName")
en tu módulo así:
@Module
public class ApplicationModule {
private Shape rec;
private Shape circle;
public ApplicationModule() {
rec = new Rectangle();
circle= new Circle ();
}
@Provides
@Named("rect")
public Shape provideRectangle() {
return rec ;
}
@Provides
@Named("circle")
public Shape provideCircle() {
return circle;
}
}
Entonces donde sea que necesites inyectarlos solo escribe
@Inject
@Named("rect")
Shape objRect;
@Qualifier
anotaciones de @Qualifier
son la forma correcta de distinguir diferentes instancias o solicitudes de inyección que tienen el mismo tipo. La página principal de la Guía del usuario tiene una sección completa sobre ellos .
@Qualifier @Retention(RUNTIME)
public interface Parallelogram {} /* name is up to you */
// In your Module:
@Provides @Parallelogram
public Shape provideRectangle() {
return rec ;
}
// In your other injected types:
@Inject @Parallelogram Shape parallelogramShape;
// or
@Inject @Parallelogram Provider<Shape> parallelogramShapeProvider;
// In your Component:
@Parallelogram Shape provideRectangle();
Aparte: aunque estoy de acuerdo con sector11 en que no debe usar new
tipos inyectados , los módulos son exactamente el lugar correcto para llamar a new
si es necesario. Aparte de agregar las anotaciones calificadoras, diría que su módulo me parece perfecto.
EDITAR con respecto al uso de @Named en comparación con las anotaciones de calificador personalizado:
- @Named es una anotación
@Qualifier
en@Qualifier
, muy parecida a la que he creado anteriormente. Para casos simples, funciona muy bien, pero como el enlace es solo una cadena, no obtendrá tanta ayuda de su IDE para detectar claves válidas o completarlas automáticamente. - Al igual que con el parámetro de cadena de Named, los calificadores personalizados pueden tener propiedades de cadena, primitiva, enumeración o literal de clase. Para las enumeraciones, los IDE a menudo pueden autocompletar valores válidos.
- Se puede acceder a
@Named
y calificadores personalizados desde las anotaciones exactamente de la misma manera especificando la anotación en el método del componente, como lo he hecho con@Parallelogram
arriba.