Le résultat de l'abonnement n'est pas utilisé

133

Je suis passé à Android Studio 3.1 aujourd'hui, ce qui semble avoir ajouté quelques autres contrôles de peluches. L'un de ces contrôles de charpie concerne les subscribe()appels RxJava2 ponctuels qui ne sont pas stockés dans une variable. Par exemple, obtenir une liste de tous les joueurs à partir de la base de données de ma salle:

Single.just(db)
            .subscribeOn(Schedulers.io())
            .subscribe(db -> db.playerDao().getAll());

Résultats dans un gros bloc jaune et cette info-bulle:

Le résultat de subscriben'est pas utilisé

Capture d'écran d'Android Studio.  Le code est surligné en jaune avec une info-bulle.  Texte de l'info-bulle: le résultat de l'abonnement n'est pas utilisé.

Quelle est la meilleure pratique pour les appels Rx one-shot comme celui-ci? Dois-je garder la main sur Disposableet dispose()à la fin? Ou devrais-je simplement @SuppressLintpasser à autre chose?

Cela semble n'affecter que RxJava2 ( io.reactivex), RxJava ( rx) n'a pas cette charpie.

Michael Dodd
la source
De vos deux solutions, je pense honnêtement que @SuppressLint n'est pas la meilleure. Peut-être que je me trompe, mais je pense vraiment que le code ne devrait jamais modifier les avertissements et / ou les conseils de l'IDE
Arthur Attout
@ArthurAttout D'accord, actuellement, je garde la main sur la Disposableportée des membres et j'appelle dispose()lorsque le single se termine, mais cela semble inutilement encombrant. Je suis intéressé de voir s'il existe de meilleures façons de faire cela.
Michael Dodd
8
Je pense que cet avertissement de charpie est ennuyeux lorsque le flux RxJava n'est pas abonné à partir d'un Activity / Fragment / ViewModel. J'ai un Completable qui peut s'exécuter en toute sécurité sans aucune considération pour le cycle de vie de l'activité, mais j'ai encore besoin de le supprimer?
EM
considérer RxLifecycle
최봉재

Réponses:

123

L'EDI ne sait pas quels effets potentiels votre abonnement peut avoir lorsqu'il n'est pas supprimé, il le traite donc comme potentiellement dangereux. Par exemple, votre Singlepeut contenir un appel réseau, ce qui pourrait provoquer une fuite de mémoire si votre Activityest abandonné pendant son exécution.

Un moyen pratique de gérer une grande quantité de Disposables consiste à utiliser un CompositeDisposable ; créez simplement une nouvelle CompositeDisposablevariable d'instance dans votre classe englobante, puis ajoutez tous vos objets jetables au CompositeDisposable (avec RxKotlin, vous pouvez simplement ajouter addTo(compositeDisposable)à tous vos objets jetables). Enfin, lorsque vous avez terminé avec votre instance, appelez compositeDisposable.dispose().

Cela éliminera les avertissements de peluche et garantira que vous Disposablesêtes bien géré.

Dans ce cas, le code ressemblerait à:

CompositeDisposable compositeDisposable = new CompositeDisposable();

Disposable disposable = Single.just(db)
        .subscribeOn(Schedulers.io())
        .subscribe(db -> db.get(1)));

compositeDisposable.add(disposable); //IDE is satisfied that the Disposable is being managed. 
disposable.addTo(compositeDisposable); //Alternatively, use this RxKotlin extension function.


compositeDisposable.dispose(); //Placed wherever we'd like to dispose our Disposables (i.e. in onDestroy()).
urgentx
la source
error: cannot find symbol method addTo(CompositeDisposable)J'obtiens une erreur de compilation avec "rxjava: 2.1.13". D'où vient cette méthode? (RxSwift ou RxKotlin je suppose)
aeracode
2
Oui, c'est une méthode RxKotlin.
urgentx
1
que faire en cas de fluide
Chasse
Et si nous faisions cela dans doOnSubscribe
Killer
2
Cela ne provoquerait pas de fuite de mémoire. Une fois l'appel réseau terminé et le onComplete appelé, le garbage collection s'occupera du reste à moins que vous n'ayez conservé une référence explicite du jetable et que vous ne le supprimiez pas.
Gabriel Vasconcelos
26

Au moment où l'activité sera détruite, la liste des produits jetables est effacée et tout va bien.

io.reactivex.disposables.CompositeDisposable mDisposable;

    mDisposable = new CompositeDisposable();

    mDisposable.add(
            Single.just(db)
                    .subscribeOn(Schedulers.io())
                    .subscribe(db -> db.get(1)));

    mDisposable.dispose(); // dispose wherever is required
Aks4125
la source
10

Vous pouvez vous abonner avec DisposableSingleObserver :

Single.just(db)
    .subscribeOn(Schedulers.io())
    .subscribe(new DisposableSingleObserver<Object>() {
            @Override
            public void onSuccess(Object obj) {
                // work with the resulting todos...
                dispose();
            }

            @Override
            public void onError(Throwable e) {
                // handle the error case...
                dispose();
            }});

Au cas où vous auriez besoin de supprimer directement l' Singleobjet (par exemple avant qu'il ne soit émis), vous pouvez implémenter une méthode onSubscribe(Disposable d)pour obtenir et utiliser la Disposableréférence.

Vous pouvez également réaliser l' SingleObserverinterface par vous-même ou utiliser d'autres classes enfants.

papandreus
la source
5

Comme cela a été suggéré, vous pouvez utiliser un global CompositeDisposablepour y ajouter le résultat de l'opération d'abonnement.

La bibliothèque RxJava2Extensions contient des méthodes utiles pour supprimer automatiquement les éléments jetables créés du CompositeDisposable. Voir la section subscribeAutoDispose .

Dans votre cas, cela peut ressembler à ceci

SingleConsumers.subscribeAutoDispose(
    Single.just(db)
            .subscribeOn(Schedulers.io()),
    composite,
    db -> db.playerDao().getAll())
Eugène Popovich
la source
2

Vous pouvez utiliser Uber AutoDispose et rxjava.as

        Single.just(db)
            .subscribeOn(Schedulers.io())
            .as(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)))
            .subscribe(db -> db.playerDao().getAll());

Assurez-vous que vous comprenez quand vous vous désabonnez en fonction du ScopeProvider.

blaffie
la source
Cela suppose qu'un fournisseur de cycle de vie est disponible. De plus, la méthode "as" est marquée comme instable, donc son utilisation entraînera un avertissement Lint.
Dabbler
1
Merci @Dabbler, d'accord. La méthode .as était expérimentale jusqu'à RxJava 2.1.7 et sur 2.2 elle est stable.
blaffie le
1

Je reviens encore et encore à la question de savoir comment disposer correctement des abonnements, et à cette publication en particulier. Plusieurs blogs et conférences affirment que le fait de ne pas appeler disposeconduit nécessairement à une fuite de mémoire, ce que je pense être une déclaration trop générale. À mon avis, l'avertissement de non-stockage du résultat subscribeest un non-problème dans certains cas, car:

  • Toutes les observables ne s'exécutent pas dans le contexte d'une activité Android
  • L'observable peut être synchrone
  • Dispose est appelé implicitement, à condition que l'observable se termine

Comme je ne veux pas supprimer les avertissements de charpie, j'ai récemment commencé à utiliser le modèle suivant pour les cas avec une observable synchrone:

var disposable: Disposable? = null

disposable = Observable
   .just(/* Whatever */)
   .anyOperator()
   .anyOtherOperator()
   .subscribe(
      { /* onSuccess */ },
      { /* onError */ },
      {
         // onComplete
         // Make lint happy. It's already disposed because the stream completed.
         disposable?.dispose()
      }
   )

Je serais intéressé par tout commentaire à ce sujet, qu'il s'agisse d'une confirmation de l'exactitude ou de la découverte d'une échappatoire.

Amateur
la source
0

Il existe un autre moyen disponible, qui évite d'utiliser les produits jetables manuellement (ajouter et supprimer des abonnements).

Vous pouvez définir un observable et cet observable recevra le contenu d'un SubjectBehaviour (au cas où vous utiliseriez RxJava). Et en passant cet observable à votre LiveData , cela devrait fonctionner. Découvrez l'exemple suivant basé sur la question initiale:

private val playerSubject: Subject<Player> = BehaviorSubject.create()

private fun getPlayer(idPlayer: String) {
        playerSubject.onNext(idPlayer)
}

private val playerSuccessful: Observable<DataResult<Player>> = playerSubject
                        .flatMap { playerId ->
                            playerRepository.getPlayer(playerId).toObservable()
                        }
                        .share()

val playerFound: LiveData<Player>
    get() = playerSuccessful
        .filterAndMapDataSuccess()
        .toLiveData()

val playerNotFound: LiveData<Unit>
    get() = playerSuccessful.filterAndMapDataFailure()
        .map { Unit }
        .toLiveData()

// These are a couple of helpful extensions

fun <T> Observable<DataResult<T>>.filterAndMapDataSuccess(): Observable<T> =
filter { it is DataResult.Success }.map { (it as DataResult.Success).data }

fun <T> Observable<DataResult<T>>.filterAndMapDataFailure(): Observable<DataResult.Failure<T>> =
filter { it is DataResult.Failure }.map { it as DataResult.Failure<T> }
Fernando Prieto
la source
-10

Si vous êtes sûr que le jetable est géré correctement, par exemple en utilisant l'opérateur doOnSubscribe (), vous pouvez ajouter ceci à Gradle:

android {
lintOptions {
     disable 'CheckResult'
}}
Ivan
la source
10
Cela supprimera cette vérification des peluches pour toutes les instances d'un résultat non vérifié. Il y a beaucoup de fois en dehors de l'exemple de l'OP où quelqu'un doit gérer le résultat retourné. Ceci utilise un marteau pour tuer une mouche.
tir38
16
Ne faites pas ça! Il y a une raison pour laquelle vous recevez ces avertissements. Si vous savez ce que vous faites (et savez que vous n'avez jamais vraiment besoin de supprimer votre abonnement), vous pouvez supprimer @SuppressLint("CheckResult")uniquement la méthode.
Victor Rendina