web-dev-qa-db-fra.com

Étendues personnalisées Dagger2: Comment fonctionnent réellement les étendues personnalisées (@ActivityScope)?

Je lis le code source de Dagger2 Component Scopes Test sur GitHub, et j'ai vu une "étendue personnalisée" définie pour les activités appelées @ActivityScope, mais je l'ai vu dans d'autres projets, y compris le module 4 CleanArchitecture qui a son @PerActivity portée.

Mais littéralement, le code du @ActivityScope l'annotation est la suivante:

import Java.lang.annotation.Retention;
import Java.lang.annotation.RetentionPolicy;

import javax.inject.Scope;

/**
 * Created by joesteele on 2/15/15.
 */
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

Et il est "magiquement" utilisable dans les modules:

@Module
public class ActivityModule {
  @Provides @ActivityScope Picasso providePicasso(ComponentTest app, OkHttpClient client) {
    return new Picasso.Builder(app)
        .downloader(new OkHttpDownloader(client))
        .listener(new Picasso.Listener() {
          @Override public void onImageLoadFailed(Picasso picasso, Uri uri, Exception e) {
            Log.e("Picasso", "Failed to load image: " + uri.toString(), e);
          }
        })
        .build();
  }
}

Ou l'exemple CleanArchitecture:

@Scope
@Retention(RUNTIME)
public @interface PerActivity {}

@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
  //Exposed to sub-graphs.
  Activity activity();
}

@Module
public class ActivityModule {
  private final Activity activity;

  public ActivityModule(Activity activity) {
    this.activity = activity;
  }

  /**
  * Expose the activity to dependents in the graph.
  */
  @Provides @PerActivity Activity activity() {
    return this.activity;
  }
}

Je peux clairement voir que cela a à voir avec les étendues personnalisées JSR-330, mais je vraiment ne comprends pas exactement ce qui se passe ici pour le rendre si que ce code permet au module donné et/ou à ce qui est fourni par un module donné de dépendre du cycle de vie réel de Activity, et pour qu'il n'existe qu'une seule instance mais seulement si cette activité donnée est active.

Les docs disent ceci:

Scope

Dagger 1 only supported a single scope: @Singleton. 
Dagger 2 allows users to any well-formed scope annotation. 
The Component docs describe the details of 
    how to properly apply scope to a component.

Il dit de regarder la page de documentation des composants , mais cela me donne 404. J'ai également vu this , mais ...

Puis-je demander de l'aide pour clarifier pourquoi la spécification de cette étendue personnalisée fait par magie Activity-level scopes travailler sans problème?

(La réponse est, une sous-étendue peut recevoir des dépendances de sa superscope, et une sous-étendue existe aussi longtemps que le composant le fait. Et que vous devez spécifier les étendues sur vos modules, et vous devez spécifier vos dépendances de composant à la sous-étendue une superscope.)

42
EpicPandaForce

Il convient de noter que, apparemment, Dagger2 crée une seule instance par fournisseur de portée dans un module par composant.

Ainsi, pour obtenir un fournisseur de portée dans un module, vous devez spécifier la portée de la méthode de fournisseur de votre module.

@Module
public class YourModule {
    @Provides
    @YourScope //one per component
    public Something something() { return new SomethingImpl(); }

    @Provides //new instance per injection
    public Otherthing otherthing() { return new OtherthingImpl(); }
}

@Component
@YourScope
public interface YourComponent {
    Something something();
    Otherthing otherthing();

    void inject(YourThing yourThing); // only if you want field injection
}

EDIT START: bien qu'en général, vous n'avez pas besoin d'instancier vos propres implémentations dans vos modules, vous pouvez donc simplement faire ceci:

@Module
public abstract class YourModule {
    @Binds
    @YourScope //one per component
    public abstract Something something(SomethingImpl impl);

    @Binds //normally new instance per injection, depends on scope of Impl
    public abstract Otherthing otherthing(OtherthingImpl impl);
}

@Singleton
public class SomethingImpl implements Something {
    @Inject
    SomethingImpl() {
    }
}

// unscoped
public class OtherThingImpl implements OtherThing {
    @Inject
    OtherThingImpl() {
    }
}

@Component
@YourScope
public interface YourComponent {
    Something something();
    Otherthing otherthing();

    void inject(YourThing yourThing); // only if you want field injection
}

EDIT END

Ensuite, référez-vous à la réponse de Kirill; essentiellement, une "portée" détermine à elle seule qu'elle est différente de l'autre. L'utilisation de dépendances de composants (ou de sous-composants) crée une sous-étendue.

@Module
public class SubModule {
    @Provides
    @SubScope
    public ThatThing thatThing() { return new ThatThingImpl(); }
}

@Component(dependencies={YourComponent.class}, modules={SubModule.class})
@SubScope
public interface SubComponent extends YourComponent {
    ThatThing thatThing();

    void inject(SubThing subThing); // only if you want field injection
}

Un composant ne peut dépendre que d'un autre composant de portée.

18
EpicPandaForce

En fait, il n'y a pas de magie. Les annotations d'étendue personnalisées ne sont que des annotations. Ils peuvent avoir n'importe quel nom.

La première fonction des étendues est un moyen d'indiquer au compilateur Dagger les étendues autorisées dans le composant étendu. C'est pourquoi utiliser @ActivityScope dépendance en non -@ActivityScope le composant déclenchera une erreur de compilation.

En fait, les composants peuvent déclarer de nombreuses étendues (par exemple @ActivityScope et @UiScope) et Dagger les traiteront tous les deux comme une seule étendue - on parle d'aliasing de portée. Par exemple, il est utile dans les projets multi-modules - lorsqu'un module Gradle définit une étendue avec ses modules Dagger et qu'un autre module Gradle définit une autre étendue, alors que les deux peuvent être utilisés comme une seule étendue aliasée dans un troisième module Gradle qui définit le composant Dagger.

La deuxième fonction consiste à limiter le nombre d'instances autorisées dans le composant de portée. Plusieurs types de portées sont pris en charge:

Non délimité - lorsqu'aucune annotation n'est déclarée. La dépendance non étendue aura un simple Provider généré sans aucune mise en cache et toute instance de cette dépendance créée dans le composant sera nouvelle pour chaque nouvelle injection (comme dans le constructeur, ou dans la méthode de fourniture de module, ou tout simplement comme un champ).

Portée personnalisée par ex. @ActivityScope annotation définie avec @javax.inject.Scope annotation - Les dépendances déclarées avec cette portée ont une mise en cache Provider avec un verrou de double vérification généré et une seule instance sera créée pour elle dans le composant déclaré avec la même portée et sa création sera thread-safe. Notez que pour chaque instance du composant lui-même, une nouvelle instance de cette dépendance sera créée.

Portée réutilisable - déclarée avec @dagger.Reusable annotation - Les dépendances déclarées avec cette étendue peuvent être partagées entre différents composants via le composant parent commun et auront la mise en cache Provider avec un verrou à vérification unique généré. Il est utile lorsque la dépendance n'a pas nécessairement besoin d'avoir une seule instance mais peut être partagée pour des performances accrues (moins d'allocations) dans un seul composant ou entre les composants.

Pour plus d'informations sur le fonctionnement des étendues, reportez-vous à guide de l'utilisateur et au code généré par Dagger.

Comment définir la portée réelle est votre prérogative. Définissez le cycle de vie de votre composant de portée, quand il est créé et quand il est détruit - c'est votre portée. Par exemple. @ActivityScope est lié au cycle de vie des activités et défini comme suit:

private ActivityComponent component;

@Override
protected void onCreate(Bundle savedInstanceState) {
    component = DaggerActivityComponent.builder().build();
    component.inject(this);
}

@Override
protected void onDestroy() {
    component = null;
    super.onDestroy();
}

Il n'y a donc pas de magie. Définissez vos étendues par la sémantique de leur utilisation. Vous pouvez également trouver utile cette réponse et ces exemples .

EDIT 14.10.2018 Développé sur les fonctions et types d'étendues pour éliminer l'ambiguïté dans la réponse précédente.

32
Kirill Boyarshinov