如何从Firebase实时数据库仅获取更新或新增对象?
解决Firebase实时数据库仅监听新增/更新对象的问题
我来帮你搞定这个Firebase实时数据库的监听问题——你碰到的是ChildEventListener的默认特性:初始化时会把当前匹配查询的所有子节点都触发一遍onChildAdded,哪怕这些数据根本没更新过。要实现只监听后续新增或更新的对象,完全可以用下面两种靠谱的方案:
方案1:通过时间戳过滤初始加载数据
这个方法的核心是给你的数据添加一个更新时间戳字段,然后在监听时只处理初始监听时间之后的事件,非常精准。
步骤说明
- 先给你的
last_chat节点数据添加一个updated_at字段,每次新增或更新数据时,用ServerValue.TIMESTAMP设置这个字段的值(确保是Firebase服务器时间,避免本地时间偏差)。 - 在初始化ChildEventListener前,记录当前的时间戳作为初始监听时间。
- 在
onChildAdded和onChildChanged中,判断数据的updated_at是否晚于初始时间,只有满足条件才处理数据。
代码示例
// 记录初始监听的时间(用本地时间也可以,推荐和Firebase服务器时间对齐) long initialListenTime = System.currentTimeMillis(); databaseReference.child("last_chat") .orderByChild("s_id") .equalTo(preference.getUserData().getId()) .limitToLast(1) .addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { // 获取数据的更新时间戳 Long dataUpdatedAt = dataSnapshot.child("updated_at").getValue(Long.class); // 只处理初始监听之后新增/更新的数据 if (dataUpdatedAt != null && dataUpdatedAt > initialListenTime) { Log.e("dataSnapShot", dataSnapshot.toString() + " " + s); } } @Override public void onChildChanged(DataSnapshot dataSnapshot, String s) { Long dataUpdatedAt = dataSnapshot.child("updated_at").getValue(Long.class); if (dataUpdatedAt != null && dataUpdatedAt > initialListenTime) { Log.e("dataSnapShotChagned", dataSnapshot.toString() + " " + s); } } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { Log.e("dataSnapShotRemoved", dataSnapshot.toString()); } @Override public void onChildMoved(DataSnapshot dataSnapshot, String s) { } @Override public void onCancelled(DatabaseError databaseError) { Log.e("onCalcnelle", databaseError.toString()); } });
方案2:通过初始数据Key过滤重复触发
如果不想修改数据结构,这个方法更直接:先获取当前匹配查询的最后一条数据的Key,然后在ChildEventListener中忽略初始加载的这条数据。
步骤说明
- 先用
addListenerForSingleValueEvent执行一次查询,拿到当前匹配的最后一条数据的Key。 - 初始化ChildEventListener后,在
onChildAdded中判断当前数据的Key是否和初始Key一致,一致就跳过(因为是初始加载的);onChildChanged可以直接处理,因为它只会在数据更新时触发。
代码示例
// 先获取初始匹配数据的Key databaseReference.child("last_chat") .orderByChild("s_id") .equalTo(preference.getUserData().getId()) .limitToLast(1) .addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { String initialDataKey = null; // 遍历子节点(因为limitToLast(1),所以只有一个) for (DataSnapshot childSnap : dataSnapshot.getChildren()) { initialDataKey = childSnap.getKey(); break; } // 初始化ChildEventListener,过滤初始数据 databaseReference.child("last_chat") .orderByChild("s_id") .equalTo(preference.getUserData().getId()) .limitToLast(1) .addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { // 忽略初始加载的那条数据 if (initialDataKey != null && !dataSnapshot.getKey().equals(initialDataKey)) { Log.e("dataSnapShot", dataSnapshot.toString() + " " + s); } } @Override public void onChildChanged(DataSnapshot dataSnapshot, String s) { // onChildChanged只会在数据更新时触发,直接处理即可 Log.e("dataSnapShotChagned", dataSnapshot.toString() + " " + s); } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { Log.e("dataSnapShotRemoved", dataSnapshot.toString()); } @Override public void onChildMoved(DataSnapshot dataSnapshot, String s) { } @Override public void onCancelled(DatabaseError databaseError) { Log.e("onCalcnelle", databaseError.toString()); } }); } @Override public void onCancelled(DatabaseError databaseError) { Log.e("initialQueryError", databaseError.toString()); } });
方案对比
- 方案1更灵活,能准确区分“初始加载”和“后续更新/新增”,哪怕初始数据在监听后马上更新也能正确捕获;但需要修改数据结构添加时间戳字段。
- 方案2无需修改数据,实现简单,但如果初始数据在设置监听后立刻被更新,
onChildChanged会正常触发(这其实是符合需求的,因为确实是更新事件)。
你可以根据自己的项目需求选择合适的方案~
内容的提问来源于stack exchange,提问作者Mehul_MG
相关产品推荐
相关产品推荐

