web-dev-qa-db-ja.com

LiveDataは最初のコールバック後にObserverを削除します

最初の結果を受け取った後にオブザーバーを削除するにはどうすればよいですか?以下に、私が試した2つのコードの方法を示しますが、オブザーバーを削除しても、どちらも更新を受信し続けます。

Observer observer = new Observer<DownloadItem>() {
        @Override
        public void onChanged(@Nullable DownloadItem downloadItem) {
            if(downloadItem!= null) {
                DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
                return;
            }
            startDownload();
            model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
        }
    };
    model.getDownloadByContentId(contentId).observeForever(observer);

 model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, downloadItem-> {
             if(downloadItem!= null) {
                this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
                return;
            }
            startDownload();
            model.getDownloadByContentId(contentId).removeObserver(downloadItem-> {});
        } );
30
galaxigirl

observeForever()はどのLifecycleOwnerにも関連付けられていないため、最初のものは機能しません。

既存の登録済みオブザーバーをremoveObserver()に渡していないため、2番目のものは機能しません。

まず、LiveData(アクティビティ)でLifecycleOwnerを使用しているかどうかを確認する必要があります。私の想定では、LifecycleOwnerを使用する必要があります。その場合は、次を使用します。

Observer observer = new Observer<DownloadItem>() {
    @Override
    public void onChanged(@Nullable DownloadItem downloadItem) {
        if(downloadItem!= null) {
            DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
            return;
        }
        startDownload();
        model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
    }
};

model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
23
CommonsWare

CommonsWare answerに続いて、removeObservers()を呼び出してLiveDataにアタッチされたすべてのオブザーバーを削除する代わりに、単にremoveObserver(this)を呼び出してこのオブザーバーのみを削除できます。

Observer observer = new Observer<DownloadItem>() {
    @Override
    public void onChanged(@Nullable DownloadItem downloadItem) {
        if(downloadItem!= null) {
            DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
            return;
        }
        startDownload();
        model.getDownloadByContentId(contentId).removeObserver(this);
    }
};

model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);

注:removeObserver(this)thisは、オブザーバインスタンスを指し、匿名の内部クラスの場合にのみ機能します。ラムダを使用する場合、thisはアクティビティインスタンスを参照します。

16
Toni Joe

Kotlinには拡張機能を備えたより便利なソリューションがあります。

fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
    observe(lifecycleOwner, object : Observer<T> {
        override fun onChanged(t: T?) {
            observer.onChanged(t)
            removeObserver(this)
        }
    })
}

この拡張機能により、次のことが可能になります。

liveData.observeOnce(this, Observer<Password> {
    if (it != null) {
        // do something
    }
})

したがって、元の質問に答えるために、それを行うことができます。

val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context, Observer<T> {
    if (it != null) {
        DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
    }
    startDownload();
})

元のソースはこちら: https://code.luasoftware.com/tutorials/Android/android-livedata-observe-once-only-kotlin/

更新:@ Hakem-Zaiedは正しいです。observeの代わりにobserveForeverを使用する必要があります。

15
Vince

上記の@vinceには同意しますが、次のようにlifecycleOwnerを渡すことをスキップし、observerForeverを使用すると思います。

fun <T> LiveData<T>.observeOnce(observer: Observer<T>) {
    observeForever(object : Observer<T> {
        override fun onChanged(t: T?) {
            observer.onChanged(t)
            removeObserver(this)
        }
    })
}

または、以下のようにlifecycleOwnerobserveと組み合わせて使用​​します。

fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
    observe(lifecycleOwner, object : Observer<T> {
        override fun onChanged(t: T?) {
            observer.onChanged(t)
            removeObserver(this)
        }
    })
}
3
Hakem Zaied

私は@Vinceと@Hakem Zaiedによる一般的なソリューションが大好きですが、私にとってはラムダバージョンはさらに良いようです:

fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit) {
    observeForever(object: Observer<T> {
        override fun onChanged(value: T) {
            removeObserver(this)
            observer(value)
        }
    })
}

fun <T> LiveData<T>.observeOnce(owner: LifecycleOwner, observer: (T) -> Unit) {
    observe(owner, object: Observer<T> {
        override fun onChanged(value: T) {
            removeObserver(this)
            observer(value)
        }
    })
}

したがって、次のようになります。

    val livedata = model.getDownloadByContentId(contentId)
    livedata.observeOnce((AppCompatActivity) context) {
        if (it != null) {
            DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists")
        }
        startDownload();
    }

私はクリーナーを見つけます。

また、removeObserver()は、オブザーバーがディスパッチされるときに最初に呼び出され、より安全になります(つまり、ユーザーのオブザーバーコード内からの潜在的なランタイムエラースローに対処します)。

2
d4vidi
  1. LiveDataクラスには、オブザーバーを削除するための2つの同様のメソッドがあります。最初の名前は

同じLifecycleOwnerのObserverのリストから削除したいオブザーバーを取り込むremoveObserver(@NonNull final Observer<T> observer)(メソッドの名前を注意深く参照してください。これは単数形です)。

  1. 2番目の方法は

removeObservers(@NonNull final LifecycleOwner owner)(複数形のメソッド名を参照)。このメソッドは、LifecycleOwner自体を取り込み、指定されたLifecycleOwnerのすべてのオブザーバーを削除します。

今、あなたの場合、あなたは2つの方法でオブザーバーを削除することができます(多くの方法があるかもしれません)、1つは前の答えで@ToniJoeによって言われます。

もう1つの方法は、ViewModelにブール値のMutableLiveDataを保持するだけです。これは、初めて観測されたときにtrueを格納し、そのLivedataも同様に観察します。そのため、trueに変わるたびに通知され、そこで特定のオブザーバーを渡すことでオブザーバーを削除できます。

1
Abhishek Kumar

@CommonsWareと@Toni Joeによって提案されたソリューションは、ViewModelのDAOクエリから最初の結果を受け取った後にオブザーバーを削除する必要があるときに、問題を解決しませんでした。しかし、次の解決策は removeObsererを呼び出した後、Livedataがオブザーバーを保持します で、私自身の直感を少し使ってトリックを行いました。

プロセスは次のとおりです。LiveModelが要求に応じて格納されるViewModelで変数を作成し、nullチェックを行った後、アクティビティのcreate observer関数呼び出しで変数を取得し、flushToDBルーチンを呼び出す前にremove observer関数を呼び出しますインポートされたクラス。つまり、私のViewModelのコードは次のようになります。

public class GameDataModel extends AndroidViewModel {
   private LiveData<Integer> lastMatchNum = null;
   .
   .
   .
   private void initLastMatchNum(Integer player1ID, Integer player2ID) {
       List<Integer> playerIDs = new ArrayList<>();
       playerIDs.add(player1ID);
       playerIDs.add(player2ID);

       lastMatchNum = mRepository.getLastMatchNum(playerIDs);
   }

 public LiveData<Integer> getLastMatchNum(Integer player1ID, Integer player2ID) {
       if (lastMatchNum == null) { initLastMatchNum(player1ID, player2ID); }
       return lastMatchNum;
   }

上記で、ViewModelのLiveData変数にデータがない場合、initLastMatchNum()を呼び出して、ビューモデル内の関数からデータを取得します。アクティビティから呼び出される関数はgetLastMatchNum()です。このルーチンは、ViewModelの変数のデータを取得します(DAOを介してリポジトリを介して取得されます)。

私のアクティビティにある次のコード

public class SomeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         .
         .
         .
        setupLastMatchNumObserver(); 
         .
         .
         .
    }

    private void setupLastMatchNumObserver() {
        if (mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).hasObservers()) {
            Log.v("Observers", "setupLastMatchNumObserver has observers...returning");
            return;
        }
        Log.v("Setting up Observers", "running mGameDataViewModel.get...observer()");
        mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer MatchNumber) {
                if (MatchNumber == null ) {
                    matchNumber = 1;
                    Log.v( "null MatchNumber", "matchNumber: " + matchNumber.toString());
                }
                else {
                    matchNumber = MatchNumber; matchNumber++;
                    Log.v( "matchNumber", "Incrementing match number: " + matchNumber.toString());
                }
                MatchNumberText.setText(matchNumber.toString());
            }
        });
    }

    private void removeObservers() {
        final LiveData<Integer> observable = mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID);
        if (observable != null && observable.hasObservers()) {
            Log.v("removeObserver", "Removing Observers");
            observable.removeObservers(this);
        }
    }

上記で何が行われているのか、1)アクティビティのonCreateメソッドでsetupLastMatchNumObserver()ルーチンを呼び出して、クラスの変数matchNumを更新します。これは、データベースに保存されているゲーム内のプレイヤー間のマッチ番号を追跡します。プレーヤーのすべてのセットは、互いに新しい試合をプレイする頻度に基づいて、データベース内で異なるマッチ番号を持ちます。このスレッドの最初の解決策は、onChangedでremoveオブザーバーを呼び出すのが奇妙に思え、プレーヤーの各移動のデータベースフラッシュのたびにTextViewオブジェクトを絶えず変更するため、少しうんざりしていました。したがって、最初の移動(つまり、1つのmatchNumber++値)の後にデータベースに新しい値があり、matchNumberが意図したとおりに機能しなかったため、onChangedが呼び出され続けたため、removeObserversは移動ごとに増分されました。 setupLastMatchNumObserver()は、ライブデータのオブザーバーが存在するかどうかを確認し、存在する場合は、各ラウンドで新しい呼び出しをインスタンス化しません。ご覧のとおり、TextViewオブジェクトを設定して、プレーヤーの現在のマッチ番号を反映しています。

次の部分は、removeObservers()を呼び出すタイミングについてのちょっとしたトリックです。最初は、アクティビティのonCreateオーバーライドでsetupLastMatchNumObserver()の直後に呼び出すと、すべてうまくいくと思いました。しかし、オブザーバーがデータを取得する前にオブザーバーを削除しました。アクティビティで収集された新しいデータを(アクティビティ全体の別のルーチンで)データベースにフラッシュする呼び出しの直前にremoveObservers()を呼び出すと、それが魅力のように機能することがわかりました。すなわち、

 public void addListenerOnButton() {
        .
        .
        .
            @Override
            public void onClick(View v) {
               .
               .
               .
               removeObservers();
               updateMatchData(data); 
            }
   }

上記の方法で、アクティビティの他の場所でremoveObservers();updateMatchData(data)を呼び出します。ビューティーはremoveObservers()オブザーバーがいない場合に戻るチェックがあるため、必要な回数だけ呼び出すことができます。

0
bubonic