1. ホーム
  2. android

[解決済み] 新しくアタッチされたオブザーバーに対して、LiveDataオブザーバーが2回トリガーされる理由

2023-05-02 15:09:46

質問

私の理解では LiveData は、データの現在の状態の変化に対してオブザーバを起動し、 データの一連の履歴の状態変化には起動しないということです。

現在、私は MainFragment を実行する Room を変更するために、書き込み操作 トラッシュされていないデータ に変更する。 トラッシュされたデータ .

また、別の TrashFragment を観察しています。 ゴミになったデータ .

次のようなシナリオを考えてみましょう。

  1. 現在、0件の ゴミ箱に捨てられたデータ .
  2. MainFragment は現在アクティブなフラグメントです。 TrashFragment はまだ作成されていません。
  3. MainFragment 追加された1 ゴミデータ .
  4. 現在、1つの ゴミになったデータ
  5. ナビゲーションドロワーを使って MainFragmentTrashFragment .
  6. TrashFragment のオブザーバは、まず onChanged で、0 ゴミになったデータ
  7. もう一度 TrashFragment のオブザーバは二番目に onChanged で、1 ゴミになったデータ

予想外なのは、(6)のようなことは起こらないはずだということです。 TrashFragment は最新の ゴミになったデータ であり、これは1である。

以下は私のコードです。


TrashFragment.java

public class TrashFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        noteViewModel = ViewModelProviders.of(getActivity()).get(NoteViewModel.class);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ...

        noteViewModel.getTrashedNotesLiveData().removeObservers(this);
        noteViewModel.getTrashedNotesLiveData().observe(this, notesObserver);

MainFragment.java

public class MainFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        noteViewModel = ViewModelProviders.of(getActivity()).get(NoteViewModel.class);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ...

        noteViewModel.getNotesLiveData().removeObservers(this);
        noteViewModel.getNotesLiveData().observe(this, notesObserver);

NoteViewModel .java

public class NoteViewModel extends ViewModel {
    private final LiveData<List<Note>> notesLiveData;
    private final LiveData<List<Note>> trashedNotesLiveData;

    public LiveData<List<Note>> getNotesLiveData() {
        return notesLiveData;
    }

    public LiveData<List<Note>> getTrashedNotesLiveData() {
        return trashedNotesLiveData;
    }

    public NoteViewModel() {
        notesLiveData = NoteplusRoomDatabase.instance().noteDao().getNotes();
        trashedNotesLiveData = NoteplusRoomDatabase.instance().noteDao().getTrashedNotes();
    }
}


ルームを処理するコード

public enum NoteRepository {
    INSTANCE;

    public LiveData<List<Note>> getTrashedNotes() {
        NoteDao noteDao = NoteplusRoomDatabase.instance().noteDao();
        return noteDao.getTrashedNotes();
    }

    public LiveData<List<Note>> getNotes() {
        NoteDao noteDao = NoteplusRoomDatabase.instance().noteDao();
        return noteDao.getNotes();
    }
}

@Dao
public abstract class NoteDao {
    @Transaction
    @Query("SELECT * FROM note where trashed = 0")
    public abstract LiveData<List<Note>> getNotes();

    @Transaction
    @Query("SELECT * FROM note where trashed = 1")
    public abstract LiveData<List<Note>> getTrashedNotes();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public abstract long insert(Note note);
}

@Database(
        entities = {Note.class},
        version = 1
)
public abstract class NoteplusRoomDatabase extends RoomDatabase {
    private volatile static NoteplusRoomDatabase INSTANCE;

    private static final String NAME = "noteplus";

    public abstract NoteDao noteDao();

    public static NoteplusRoomDatabase instance() {
        if (INSTANCE == null) {
            synchronized (NoteplusRoomDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(
                            NoteplusApplication.instance(),
                            NoteplusRoomDatabase.class,
                            NAME
                    ).build();
                }
            }
        }

        return INSTANCE;
    }
}

を受信しないようにする方法はありますか? onChanged を2回受け取らないようにする方法はありますか?


デモ

この問題を実証するために、デモプロジェクトを作成しました。

ご覧のように、書き込み操作( ゴミ箱に入れたノートを追加 ボタンをクリック) した後 MainFragment に切り替えたとき TrashFragment に切り替えたとき、私は onChangedTrashFragment は一度だけ呼び出されます。しかし、2回呼び出されています。

デモプロジェクトは以下からダウンロードできます。 https://github.com/yccheok/live-data-problem

どのように解決するのですか?

私はあなたのプロジェクトをフォークして、少しテストしました。私が言えることは、あなたは重大なバグを発見したということです。

再現と調査を容易にするために、私はあなたのプロジェクトを少し編集しました。更新されたプロジェクトをここで見ることができます。 https://github.com/techyourchance/live-data-problem . また、あなたのレポにプルリクエストをオープンしました。

これが気づかれないようにするために、私はまた 課題を作成しました。 を Google の課題追跡システムで公開しました。

再現のための手順

  1. MainFragmentでREPRODUCE_BUGがtrueに設定されていることを確認する。
  2. アプリをインストールする
  3. ゴミ箱のメモを追加するボタンをクリックします。
  4. TrashFragmentに切り替えます。
  5. 正しい値を持つLiveDataの通知フォームが1つだけあったことに注意してください。
  6. MainFragmentに切り替えます。
  7. ゴミ箱に入れたノートを追加するボタンをクリックします。
  8. TrashFragmentに切り替えます。
  9. LiveData から 2 つの通知があり、1 つ目の通知は不正確な値であることに注意してください。

REPRODUCE_BUG を false に設定すると、バグが再現されないことに注意してください。 再現されません。のLiveDataへのサブスクリプションが、TrashFragmentの挙動を変えたことを実証しています。 のサブスクリプションがTrashFragmentの動作を変更したことを示しています。

期待した結果です。どのような場合でも正しい値で1つだけ通知されます。 以前のサブスクリプションによる動作の変更はありません。

さらに詳しい情報です。ソースを少し見てみましたが、どうやら LiveData のアクティブ化と新しいオブザーバー サブスクリプションの両方が原因で Observerのサブスクリプションの両方によって引き起こされる通知のようです。ComputableLiveDataがonActive()計算をExecuteにオフロードする方法と関係があるかもしれません。 がonActive()の計算をExecutorにオフロードする方法と関係があるかもしれません。