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.

Michael Dodd
fonte
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()).
urgente
fonte
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
Aks4125
fonte
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.

papandreus
fonte
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())
Eugene Popovich
fonte
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.

blaffie
fonte
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.

Dabbler
fonte
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> }
Fernando Prieto
fonte
-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'
}}
Ivan
fonte
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