当前位置: 首页 > 知识库问答 >
问题:

为什么LiveData observer被触发两次用于新附加的观察器

林国安
2023-03-14

我对livedata的理解是,它将触发观察者对当前数据的状态变化,而不是对一系列历史数据的状态变化。

目前,我有一个mainfragment,它执行room写操作,将非垃圾数据更改为垃圾数据。

我还提供了另一个trashfragment,它观察被丢弃的数据。

请考虑以下场景。

  1. 当前有0个被丢弃的数据。
  2. mainfragment是当前活动的片段。trashfragment尚未创建。
  3. mainfragment添加了%1个废数据。
  4. 现在,有%1个被丢弃的数据
  5. 我们使用导航抽屉,将mainfragment替换为trashfragment
  6. trashfragment的观察者将首先接收onchanged,其中有0个被丢弃的数据
  7. 同样,trashfragment的观察者将第二次接收onchanged,其中有1个被丢弃的数据

出乎我意料的是,第(6)项不应该发生。trashfragment应只接收最新的已废弃数据,即%1。

这是我的密码

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);
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);
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吗?

我创建了一个演示项目来演示这个问题。

如您所见,我在mainfragment执行写操作(单击ADD TRASHED NOTE按钮)后,当我切换到trashfragment时,我希望trashfragment中的onchanged只会被调用一次。然而,它正在被调用两次。

演示项目可从https://github.com/yccheok/live-data-problem下载

共有2个答案

家浩瀚
2023-03-14

我只在您的代码中引入了一个更改:

noteViewModel = ViewModelProviders.of(this).get(NoteViewModel.class);

而不是:

noteViewModel = ViewModelProviders.of(getActivity()).get(NoteViewModel.class);

fragmentoncreate(Bundle)方法中。现在它工作得天衣无缝。

在您的版本中,您获得了两个片段共同的NoteViewModel引用(来自activity)。我想,viewmodel在前面的片段中注册了observer。因此,LiveData保留了对Observer的引用(在MainFragmentTrashFragment中),并调用了这两个值。

因此,我想结论可能是,您应该从viewmodelproviders中获取viewmodel,其来源是:

    fragment中的
  • fragment activity
  • 中的 activity

顺便说一句。

NoteViewModel.GetTrashedNotesLiveData().RemoveObservers(this);

在片段中不是必需的,但是我建议将其放在onstop中。

潘楚
2023-03-14

我分叉了你的项目并测试了一下。据我所知,你发现了一个严重的窃听器。

为了使复制和调查更容易,我对你的项目做了一点编辑。您可以在这里找到更新的项目:https://github.com/techyourchance/live-data-problem。我还开了一个拉请求回你的回购。

为了确保这一点不会被忽视,我还在谷歌的问题追踪器中打开了一个问题:

再现的步骤:

  1. 确保在MainFragment中将REPRODUCE_BUG设置为true
  2. 安装应用程序
  3. 单击“添加废弃便笺”按钮
  4. 切换到TrashFragment
  5. 注意,只有一个通知表单LiveData具有正确的值
  6. 切换到MainFragment
  7. 单击“添加废弃便笺”按钮
  8. 切换到TrashFragment
  9. 注意LiveData有两个通知,第一个的值不正确

注意,如果您将REPRODUCE_BUG设置为false,那么bug将不会重现。它演示了对MainFragment中的LiveData的订阅改变了TrashFragment中的行为。

预期结果:在任何情况下只有一个具有正确值的通知。没有因为以前的订阅而改变行为。

更多信息:我看了一下源代码,它看起来像是由于LiveData激活和新的Observer订阅而触发的通知。可能与ComputableLiveData将onActive()计算卸载到Executor的方式有关。

 类似资料:
  • 我将Ionic3与一个一起使用。我有下面的函数,出于某种原因,即使该函数只被调用一次,第三行也会被触发两次。 问题 这引起了一个问题,因为正在获取第二个订阅的值,并且第一个订阅丢失,不允许我取消订阅。 问题 如何创建一个只有一个订阅的

  • 问题内容: 我有一个带有Spring和Spring安全性的Web项目。我的web.xml: 在服务器日志中,我看到Spring上下文被加载了两次(Spring BeanDispatcherServletContextL。我该如何解决? 在本教程中,我看到如果提供了,则不需要。但是,如果我删除了初始化参数,则会出现错误:“ :无法打开资源 ”。Dispather Servlet在默认位置找到上下文配

  • 问题内容: 输出: 一些数据 完成X 1 一些数据 完成X 2 是我的使用错误还是..? 问题答案: 该API: 是不稳定 关于重复的通知,他已经知道“行为”。具体来说,Windows案例是Windows设计的结果,其中单个文件修改可以是对Windows API的多次调用

  • 我是前端的新手,我正在尝试实现一个简单的功能:检索城市列表,为每个城市渲染一个按钮,并单独捕获它们的点击动作 我得到的列表如下: 并像这样渲染按钮 但是,这些按钮在呈现时将被单击两次,并且如果我之后单击它们,则不会记录任何内容

  • 试图让LiveData与Room一起工作: 因为我观察到tdao.getall(),所以我希望在我的活动中,当同步完成时,它会刷新日历。现在,您必须重新加载该活动,以获取在执行同步后放入数据库的数据。我对Room和LiveData很陌生。有什么想法吗?希望我没有拿出太多的代码,这是从一个相当大的应用程序到目前为止。

  • 问题内容: window.onload = function(){ 这是我的代码,当我单击“文本”时,它将向您提示两次,但是当我单击该框时,该元素将仅触发一次,为什么? 问题答案: 当您单击标签时,它会触发单击处理程序,并且您会收到警报。 但是单击标签还会自动将click事件发送到关联的输入元素,因此将其视为对复选框的单击。然后事件冒泡会导致在包含元素(即标签)上触发click事件,因此您的处理程