O resultado da assinatura não é usado


133

Atualizei para o Android Studio 3.1 hoje, o que parece ter adicionado mais algumas verificações. Uma dessas verificações de fiapos é para subscribe()chamadas RxJava2 de uma só vez que não são armazenadas em uma variável. Por exemplo, obtendo uma lista de todos os jogadores do banco de dados da minha sala:

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

Resulta em um grande bloco amarelo e esta dica de ferramenta:

O resultado de subscribenão é usado

Captura de tela do Android Studio.  O código é destacado em amarelo com uma dica de ferramenta.  Texto da dica de ferramenta: O resultado da assinatura não é usado.

Qual é a melhor prática para chamadas Rx únicas como essa? Devo manter a preensão do Disposablee dispose()em completa? Ou devo apenas @SuppressLintseguir em frente?

Isso parece afetar apenas RxJava2 ( io.reactivex), RxJava ( rx) não possui esse fiapo.


Das duas soluções, sinceramente, acho que o @SuppressLint não é o melhor. Talvez eu esteja errado, mas eu realmente acho que o código nunca deve alterar as advertências e / ou sugestões IDE
Arthur Attout

@ArthurAttout Concordo, atualmente estou mantendo o Disposableescopo de membros at e chamando dispose()quando o single é concluído, mas parece desnecessariamente complicado. Estou interessado em ver se há alguma maneira melhor de fazer isso.
Michael Dodd

8
Eu acho que esse aviso de fiapo é irritante quando o fluxo RxJava não é inscrito em um Activity / Fragment / ViewModel. Tenho um Completável que pode ser executado com segurança, sem levar em consideração o ciclo de vida da Atividade, mas ainda preciso descartá-lo?
EM

considere RxLifecycle
#

Respostas:


122

O IDE não sabe quais os efeitos potenciais que sua assinatura pode ter quando não é descartada; portanto, trata-a como potencialmente insegura. Por exemplo, você Singlepode conter uma chamada de rede, o que pode causar um vazamento de memória se você Activityfor abandonado durante a execução.

Uma maneira conveniente de gerenciar uma grande quantidade de Disposables é usar um CompositeDisposable ; basta criar uma nova CompositeDisposablevariável de instância em sua classe anexa e adicionar todos os seus Disposables ao CompositeDisposable (com o RxKotlin, você pode simplesmente anexar addTo(compositeDisposable)todos os seus Disposables). Por fim, quando terminar sua instância, ligue compositeDisposable.dispose().

Isso eliminará os avisos de fiapos e garantirá que você Disposablesseja gerenciado corretamente.

Nesse caso, o código seria semelhante a:

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

Estou recebendo erro de compilação error: cannot find symbol method addTo(CompositeDisposable)com "rxjava: 2.1.13". De onde vem esse método? (RxSwift ou RxKotlin, suponho)
aeracode

2
Sim, é um método RxKotlin.
Urgentx

1
o que fazer em caso de fluido
Caça

E se estamos fazendo isso em doOnSubscribe
Assassino

2
Não causaria vazamento de memória. Quando a chamada de rede terminar e o onComplete for chamado, a coleta de lixo lidará com o restante, a menos que você tenha mantido uma referência explícita do descartável e não o descartado.
Gabriel Vasconcelos

26

No momento em que a atividade será destruída, a lista de descartáveis ​​é limpa e nós estamos bem.

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

9

Você pode se inscrever com 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();
            }});

Caso você precise descartar diretamente o Singleobjeto (por exemplo, antes que ele seja emitido), você pode implementar o método onSubscribe(Disposable d)para obter e usar a Disposablereferência.

Você também pode realizar a SingleObserverinterface por conta própria ou usar outras classes filho.


5

Como foi sugerido, você pode usar um pouco de global CompositeDisposablepara adicionar o resultado da operação de inscrição lá.

A biblioteca RxJava2Extensions contém métodos úteis para remover automaticamente o descartável criado do CompositeDisposablequando ele é concluído. Consulte a seção subscribeAutoDispose .

No seu caso, pode ser assim

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

2

Você pode usar o Uber AutoDispose e o rxjava.as

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

Certifique-se de entender quando cancelar a inscrição com base no ScopeProvider.


Isso pressupõe que um provedor de ciclo de vida esteja disponível. Além disso, o método "as" é marcado como instável, portanto, usá-lo resultará em um aviso de cotão.
Dabbler 28/01

1
Obrigado @Dabbler, concordou. O método .as foi experimental até o RxJava 2.1.7 e no 2.2 é estável.
blaffie 29/01

1

Repetidas vezes, volto à questão de como descartar corretamente as assinaturas e a esta publicação em particular. Vários blogs e conversas afirmam que não chamar disposenecessariamente leva a um vazamento de memória, o que eu acho que é uma afirmação muito geral. Em meu entendimento, o aviso de não utilização do resultado de subscribenão é um problema em alguns casos, porque:

  • Nem todos os observáveis ​​são executados no contexto de uma atividade do Android
  • O observável pode ser síncrono
  • Dispose é chamado implicitamente, desde que o observável seja concluído

Como não quero suprimir avisos de fiapos, recentemente comecei a usar o seguinte padrão para casos com um observável síncrono:

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

Eu estaria interessado em quaisquer comentários sobre isso, independentemente de ser uma confirmação de correção ou a descoberta de uma brecha.


0

Existe outra maneira disponível, que é evitar o uso manual de descartáveis ​​(adicionar e remover assinaturas).

Você pode definir um Observável e esse observável receberá o conteúdo de um SubjectBehaviour (caso você use RxJava). E passando isso observável ao seu LiveData , isso deve funcionar. Confira o próximo exemplo com base na pergunta 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> }

-10

Se você tem certeza de que o descartável foi tratado corretamente, por exemplo, usando o operador doOnSubscribe (), você pode adicioná-lo ao Gradle:

android {
lintOptions {
     disable 'CheckResult'
}}

10
Isso suprimirá essa verificação de fiapos para todas as instâncias de um resultado não verificado. Há muitas vezes fora do exemplo do OP em que alguém deve lidar com o resultado retornado. Isso está usando uma marreta para matar uma mosca.
Tir38

16
Por favor, não faça isso! Há uma razão para você receber esses avisos. Se você souber o que está fazendo (e realmente nunca precisar descartar sua assinatura), poderá suprimir @SuppressLint("CheckResult")apenas o método.
Victor Rendina
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.