Firebase数据变更时更新Android ArrayList的问题及优化建议
首先咱们来定位你代码里的核心问题:
在onChildRemoved方法中,你通过keysArrayList.indexOf(removedKey)获取要删除元素的索引,但如果这个key不在keysArrayList里(比如网络延迟导致onChildRemoved比对应的onChildAdded先触发,或者出现重复删除的场景),indexOf会返回**-1**。这时候调用namesArrayList.remove(-1)就会直接抛出ArrayIndexOutOfBoundsException——也就是你碰到的length=12 index=-1这类错误。
另外还有个小坑:你删除keysArrayList元素时用的是keysArrayList.remove(removedKey),这个方法会删除第一个匹配的key,虽然Firebase的key是唯一的,但逻辑上仍有导致和namesArrayList索引错位的风险。
下面给你几个从修复到优化的方案,按需选择:
方案一:快速修复现有代码,添加索引有效性检查
给删除逻辑加个判断,确保索引合法再执行操作,同时改用索引删除keysArrayList的元素:
@Override public void onChildRemoved(DataSnapshot dataSnapshot) { String removedKey = dataSnapshot.getKey(); int index = keysArrayList.indexOf(removedKey); // 先检查索引是否有效,避免-1的情况 if (index != -1) { namesArrayList.remove(index); keysArrayList.remove(index); // 用索引删除,保证和namesArrayList的操作完全对应 arrayAdapter.notifyDataSetChanged(); } }
另外还要注意:你在onCreate里重新定义了一个局部的arrayAdapter变量,这会导致类成员的arrayAdapter还是null,调用notifyDataSetChanged()会抛空指针!要把局部变量的声明去掉,直接赋值给类成员:
// 去掉前面的ArrayAdapter<String>,直接给类成员赋值 arrayAdapter = new ArrayAdapter<>(this,android.R.layout.activity_list_item,namesArrayList);
方案二:用HashMap替代双ArrayList,避免数据不同步
双ArrayList的方式很容易出现两个列表不同步的问题,推荐用HashMap<String, String>来维护key和name的映射,再把values转成ArrayList给Adapter:
// 替换原来的两个ArrayList public static HashMap<String, String> dataMap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_join); databaseReference = FirebaseDatabase.getInstance().getReference().child("Queue Codes"); listView = (ListView) findViewById(R.id.listViewForMember); dataMap = new HashMap<>(); // 初始化Adapter,传入空的ArrayList arrayAdapter = new ArrayAdapter<>(this, android.R.layout.activity_list_item, new ArrayList<>()); listView.setAdapter(arrayAdapter); databaseReference.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { String key = dataSnapshot.getKey(); String name = dataSnapshot.getValue(String.class); dataMap.put(key, name); updateAdapterData(); } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { String removedKey = dataSnapshot.getKey(); dataMap.remove(removedKey); updateAdapterData(); } // 其他方法(onChildChanged、onChildMoved、onCancelled)保持原样即可 }); } // 单独封装更新Adapter的方法,代码更清晰 private void updateAdapterData() { arrayAdapter.clear(); arrayAdapter.addAll(dataMap.values()); arrayAdapter.notifyDataSetChanged(); }
这种方式的好处:
- 不会出现两个列表不同步的问题,HashMap天然维护key-value的对应关系
- 查找和删除操作效率更高(HashMap的remove是O(1),而ArrayList的indexOf是O(n))
- 代码更简洁,不用手动维护索引对应关系
方案三:使用Firebase官方的FirebaseListAdapter(最推荐)
Firebase提供了专门的FirebaseListAdapter,可以自动帮你处理数据的增删改查,完全不用手动维护ArrayList,代码会更简洁,也避免自己维护数据带来的各种错误:
首先确保你的项目依赖中添加了Firebase UI库(如果还没加的话),然后修改代码:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_join); databaseReference = FirebaseDatabase.getInstance().getReference().child("Queue Codes"); listView = (ListView) findViewById(R.id.listViewForMember); // 创建FirebaseListAdapter,自动监听数据变化 FirebaseListAdapter<String> adapter = new FirebaseListAdapter<String>( this, String.class, android.R.layout.activity_list_item, databaseReference ) { @Override protected void populateView(View v, String model, int position) { // 将数据绑定到列表项的TextView上 TextView textView = v.findViewById(android.R.id.text1); textView.setText(model); } }; listView.setAdapter(adapter); }
这种方式的优势:
- 完全不用手动处理
ChildEventListener,FirebaseListAdapter会自动监听数据变化并更新列表 - 彻底避免了手动维护数据集合可能带来的错误(比如索引越界、数据不同步、空指针)
- 代码量大幅减少,逻辑更清晰
内容的提问来源于stack exchange,提问作者Vasanth Tulasi

