El resultado de la suscripción no se usa

133

Actualicé a Android Studio 3.1 hoy, que parece haber agregado algunas comprobaciones de pelusas más. Una de estas comprobaciones de pelusa es para subscribe()llamadas RxJava2 de una sola vez que no están almacenadas en una variable. Por ejemplo, obtener una lista de todos los jugadores de la base de datos de mi sala:

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

Resultados en un gran bloque amarillo y esta información sobre herramientas:

El resultado de subscribeno se utiliza

Captura de pantalla de Android Studio.  El código se resalta en amarillo con una información sobre herramientas.  Texto de información sobre herramientas: no se utiliza el resultado de la suscripción.

¿Cuál es la mejor práctica para llamadas Rx one-shot como esta? ¿Debo mantener el Disposabley dispose()en completa? ¿O debería simplemente @SuppressLintseguir adelante?

Esto solo parece afectar a RxJava2 ( io.reactivex), RxJava ( rx) no tiene esta pelusa.

Michael Dodd
fuente
De ambas soluciones, honestamente creo que @SuppressLint no es la mejor. Tal vez me equivoque, pero realmente creo que el código nunca debería alterar las advertencias y / o sugerencias de IDE
Arthur Attout el
@ArthurAttout De acuerdo, actualmente mantengo el Disposablealcance de los miembros y llamo dispose()cuando se completa el single, pero parece innecesariamente engorroso. Estoy interesado en ver si hay mejores formas de hacerlo.
Michael Dodd
8
Creo que esta advertencia de pelusa es molesta cuando la transmisión RxJava no está suscrita desde un Activity / Fragment / ViewModel. Tengo un Completable que se puede ejecutar de forma segura sin tener en cuenta el ciclo de vida de la Actividad, pero ¿aún así tengo que deshacerme de él?
EM
considere RxLifecycle
최봉재

Respuestas:

122

El IDE no sabe qué efectos potenciales puede tener su suscripción cuando no se elimina, por lo que la trata como potencialmente insegura. Por ejemplo, Singlepuede contener una llamada de red, lo que podría causar una pérdida de memoria si Activityse abandona durante su ejecución.

Una manera conveniente de administrar una gran cantidad de Disposables es usar un CompositeDisposable ; simplemente cree una nueva CompositeDisposablevariable de instancia en su clase adjunta, luego agregue todos sus Desechables a CompositeDisposable (con RxKotlin puede agregar addTo(compositeDisposable)a todos sus Desechables). Finalmente, cuando haya terminado con su instancia, llame compositeDisposable.dispose().

Esto eliminará las advertencias de pelusa y garantizará que Disposablesse administren correctamente.

En este caso, el código se vería así:

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()).
urgentex
fuente
Recibo un error de compilación error: cannot find symbol method addTo(CompositeDisposable)con "rxjava: 2.1.13". ¿De dónde viene este método? (RxSwift o RxKotlin, supongo)
aeracode
2
Sí, es un método RxKotlin.
urgente
1
qué hacer en caso de fluidez
Hunt
¿Qué pasa si estamos haciendo esto en doOnSubscribe
Killer
2
No causaría una pérdida de memoria. Una vez que finaliza la llamada de red y se llama a onComplete, la recolección de basura se encargará del resto a menos que haya mantenido una referencia explícita de lo desechable y no lo deseche.
Gabriel Vasconcelos
26

En el momento en que se destruya la Actividad, la lista de Desechables se borra y estamos 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
fuente
9

Puede suscribirse con 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();
            }});

En caso de que necesite disponer directamente del Singleobjeto (por ejemplo, antes de que se emita), puede implementar un método onSubscribe(Disposable d)para obtener y usar la Disposablereferencia.

También puede realizar la SingleObserverinterfaz por su cuenta o utilizar otras clases secundarias.

papandreus
fuente
5

Como se sugirió, puede usar algo global CompositeDisposablepara agregar el resultado de la operación de suscripción allí.

La biblioteca RxJava2Extensions contiene métodos útiles para eliminar automáticamente los elementos desechables creados CompositeDisposablecuando se completa. Consulte la sección subscribeAutoDispose .

En su caso, puede verse así

SingleConsumers.subscribeAutoDispose(
    Single.just(db)
            .subscribeOn(Schedulers.io()),
    composite,
    db -> db.playerDao().getAll())
Eugene Popovich
fuente
2

Puede usar Uber AutoDispose y rxjava.as

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

Asegúrese de comprender cuando se da de baja en base a ScopeProvider.

blaffie
fuente
Esto supone que hay un proveedor de ciclo de vida disponible. Además, el método "como" está marcado como inestable, por lo que su uso generará una advertencia de pelusa.
Dabbler
1
Gracias @Dabbler, de acuerdo. El método .as fue experimental hasta RxJava 2.1.7 y en 2.2 es estable.
Blaffie
1

Una y otra vez me encuentro volviendo a la cuestión de cómo deshacerme correctamente de las suscripciones, y a esta publicación en particular. Varios blogs y charlas afirman que no llamar disposenecesariamente conduce a una pérdida de memoria, lo cual creo que es una declaración demasiado general. Según tengo entendido, la advertencia de pelusa sobre no almacenar el resultado subscribeno es un problema en algunos casos, porque:

  • No todos los observables se ejecutan en el contexto de una actividad de Android
  • Lo observable puede ser sincrónico
  • Dispose se llama implícitamente, siempre que el observable complete

Como no quiero suprimir las advertencias de pelusas, recientemente comencé a usar el siguiente patrón para casos con un observable sincrónico:

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()
      }
   )

Me interesaría cualquier comentario sobre esto, independientemente de si se trata de una confirmación de corrección o el descubrimiento de una escapatoria.

Aficionado
fuente
0

Hay otra forma disponible, que es evitar el uso de Desechables manualmente (agregar y eliminar suscripciones).

Puede definir un Observable y ese observable recibirá el contenido de un SubjectBehaviour (en caso de que use RxJava). Y al pasar eso observable a su LiveData , eso debería funcionar. Mira el siguiente ejemplo basado en la pregunta inicial:

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
fuente
-10

Si está seguro de que el desechable se manejó correctamente, por ejemplo, utilizando el operador doOnSubscribe (), puede agregar esto a Gradle:

android {
lintOptions {
     disable 'CheckResult'
}}
Ivan
fuente
10
Esto suprimirá esta comprobación de pelusas para todas las instancias de un resultado no verificado. Hay muchas veces fuera del ejemplo del OP en el que alguien debe manejar el resultado devuelto. Esto está usando un mazo para matar una mosca.
tir38
16
¡Por favor no hagas esto! Hay una razón por la que recibe estas advertencias. Si sabe lo que está haciendo (y sabe que realmente no necesita deshacerse de su suscripción), puede suprimir @SuppressLint("CheckResult")solo el método.
Victor Rendina