Android自定义ListView ArrayAdapter首次启动不加载Firebase数据问题
问题根源分析
嘿,我帮你梳理下代码里的核心问题,正是这些问题导致了首次启动无法正确加载Firebase数据:
1. Firebase监听器不该放在getView()里啊!
getView()是ListView用来生成、复用item视图的方法,滚动列表或者初始化时会被频繁调用。你现在给每个item都注册了Firebase的ValueEventListener,这会带来一堆问题:
- 重复注册大量监听器,既浪费资源,还可能导致数据更新混乱
- 首次启动时Firebase数据加载是异步的,等数据返回时,对应的item视图可能已经绘制完成或者被复用了,这时候修改控件状态根本触发不了列表刷新
- 清缓存后重启,Firebase会从本地缓存读数据,速度快很多,刚好赶在视图绘制前完成状态修改,所以看起来正常,但这只是巧合
2. 数据集更新的逻辑完全错了
你的Adapter初始化时用的是默认数据,之后没有更新整个数据集,只是直接改单个item的控件状态。notifyDataSetChanged()只有在数据集本身发生变化的时候调用才有用,你现在只是改了视图,没碰dataSet,所以这个方法等于白调用。
3. (从代码片段看)ViewHolder没绑定控件
你的代码里看不到viewHolder给pendingResponse、trackLayout这些控件赋值的代码(比如viewHolder.pendingResponse = convertView.findViewById(R.id.xxx)),这要么会触发空指针异常,要么控件状态修改根本不生效,这也是首次启动异常的潜在原因。
解决方案
第一步:把Firebase数据逻辑移到Activity/Fragment里
Adapter就该干视图渲染的活,数据获取和更新交给上层组件(Activity或Fragment)来做,这样逻辑更清晰:
// 在你的Activity/Fragment中 private ArrayList<SaveFilteredContactDetails> contactList; private SaveFilteredContactsDetailsListAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.your_layout); // 先初始化默认数据 contactList = new ArrayList<>(); // 这里添加你的默认数据... // 创建Adapter并绑定到ListView adapter = new SaveFilteredContactsDetailsListAdapter(contactList, this); ListView listView = findViewById(R.id.your_listview_id); listView.setAdapter(adapter); // 从Firebase拉数据并更新数据集 firebaseInitializations(); mDatabaseRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { // 先清空旧数据 contactList.clear(); // 遍历Firebase数据,解析成SaveFilteredContactDetails对象 for(DataSnapshot ds : dataSnapshot.getChildren()){ SaveFilteredContactDetails contact = new SaveFilteredContactDetails(); contact.set_idFromFirebase(ds.getKey()); // 或者根据你的实际字段获取 // 解析接收请求的状态 if(ds.child("requests").child(UserID).hasChild(contact.get_idFromFirebase())){ RequestManager req = ds.child("requests").child(UserID).child(contact.get_idFromFirebase()).getValue(RequestManager.class); contact.setReceivedReqStatus(req.getReqStatus()); } // 解析发送请求的状态 if(ds.child("requests").child(contact.get_idFromFirebase()).hasChild(UserID)){ RequestManager req = ds.child("requests").child(contact.get_idFromFirebase()).child(UserID).getValue(RequestManager.class); contact.setSentReqStatus(req.getReqStatus()); } // 解析在线状态 if(ds.child("onOffStatus").hasChild(contact.get_idFromFirebase())){ RequestManager status = ds.child("onOffStatus").child(contact.get_idFromFirebase()).getValue(RequestManager.class); contact.setOnline(status.isOnlineOfflineStatus()); } contactList.add(contact); } // 通知Adapter数据变了,刷新列表 adapter.notifyDataSetChanged(); } @Override public void onCancelled(DatabaseError databaseError) { // 这里处理错误,比如弹个Toast提示 } }); }
第二步:修改Adapter,只做视图绑定
把Firebase相关的逻辑全删掉,Adapter只负责根据数据模型渲染视图:
public class SaveFilteredContactsDetailsListAdapter extends ArrayAdapter<SaveFilteredContactDetails> implements View.OnClickListener{ Context mContext; ArrayList<SaveFilteredContactDetails> dataSet; public SaveFilteredContactsDetailsListAdapter(ArrayList<SaveFilteredContactDetails> data, Context context) { super(context, R.layout.textforcontacts, data); this.dataSet = data; this.mContext = context; } // 完善ViewHolder,绑定所有需要的控件 private static class ViewHolder { TextView pendingResponse; View trackLayout; TextView unBlockedResponse; TextView releaseRequests; TextView pendingRequests; TextView sendRequests; TextView blockedResponse; View onOffStatusDot; } @Override public View getView(int position, View convertView, ViewGroup parent) { SaveFilteredContactDetails dataModel = getItem(position); ViewHolder viewHolder; final View result; if (convertView == null) { viewHolder = new ViewHolder(); LayoutInflater inflater = LayoutInflater.from(getContext()); convertView = inflater.inflate(R.layout.textforcontacts, parent, false); // 这里一定要绑定控件!替换成你实际的ID viewHolder.pendingResponse = convertView.findViewById(R.id.pending_response); viewHolder.trackLayout = convertView.findViewById(R.id.track_layout); viewHolder.unBlockedResponse = convertView.findViewById(R.id.unblocked_response); viewHolder.releaseRequests = convertView.findViewById(R.id.release_requests); viewHolder.pendingRequests = convertView.findViewById(R.id.pending_requests); viewHolder.sendRequests = convertView.findViewById(R.id.send_requests); viewHolder.blockedResponse = convertView.findViewById(R.id.blocked_response); viewHolder.onOffStatusDot = convertView.findViewById(R.id.on_off_dot); convertView.setTag(viewHolder); result = convertView; } else { viewHolder = (ViewHolder) convertView.getTag(); result = convertView; } // 根据dataModel里的状态设置控件可见性 // 处理接收请求的状态 String receivedStatus = dataModel.getReceivedReqStatus(); if("received".equals(receivedStatus)) { viewHolder.pendingResponse.setVisibility(View.VISIBLE); viewHolder.trackLayout.setVisibility(View.GONE); viewHolder.unBlockedResponse.setVisibility(View.GONE); } else if("cancel".equals(receivedStatus) || "released".equals(receivedStatus) || "unblocked".equals(receivedStatus)) { viewHolder.pendingResponse.setVisibility(View.GONE); viewHolder.trackLayout.setVisibility(View.GONE); viewHolder.unBlockedResponse.setVisibility(View.GONE); } else if("blocked".equals(receivedStatus)) { viewHolder.pendingResponse.setVisibility(View.GONE); viewHolder.trackLayout.setVisibility(View.GONE); viewHolder.unBlockedResponse.setVisibility(View.VISIBLE); } else if("accepted".equals(receivedStatus)) { viewHolder.pendingResponse.setVisibility(View.GONE); viewHolder.trackLayout.setVisibility(View.VISIBLE); viewHolder.unBlockedResponse.setVisibility(View.GONE); } // 处理发送请求的状态 String sentStatus = dataModel.getSentReqStatus(); if("received".equals(sentStatus)) { viewHolder.releaseRequests.setVisibility(View.GONE); viewHolder.pendingRequests.setVisibility(View.VISIBLE); viewHolder.sendRequests.setVisibility(View.GONE); viewHolder.blockedResponse.setVisibility(View.GONE); } else if("accepted".equals(sentStatus)) { viewHolder.pendingRequests.setVisibility(View.GONE); viewHolder.sendRequests.setVisibility(View.GONE); viewHolder.releaseRequests.setVisibility(View.VISIBLE); viewHolder.blockedResponse.setVisibility(View.GONE); } else if("rejected".equals(sentStatus) || "unblocked".equals(sentStatus)) { viewHolder.releaseRequests.setVisibility(View.GONE); viewHolder.pendingRequests.setVisibility(View.GONE); viewHolder.sendRequests.setVisibility(View.VISIBLE); viewHolder.blockedResponse.setVisibility(View.GONE); } else if("blocked".equals(sentStatus)) { viewHolder.releaseRequests.setVisibility(View.GONE); viewHolder.pendingRequests.setVisibility(View.GONE); viewHolder.sendRequests.setVisibility(View.GONE); viewHolder.blockedResponse.setVisibility(View.VISIBLE); } // 处理在线状态 viewHolder.onOffStatusDot.setVisibility(dataModel.isOnline() ? View.VISIBLE : View.GONE); return result; } @Override public void onClick(View v) { // 这里处理你的点击事件逻辑 } }
第三步:给数据模型加字段
更新SaveFilteredContactDetails,添加存储状态的字段和对应的getter/setter:
public class SaveFilteredContactDetails { private String idFromFirebase; private String receivedReqStatus; // 接收的请求状态 private String sentReqStatus; // 发送的请求状态 private boolean isOnline; // 在线状态 // getter和setter方法 public String get_idFromFirebase() { return idFromFirebase; } public void set_idFromFirebase(String idFromFirebase) { this.idFromFirebase = idFromFirebase; } public String getReceivedReqStatus() { return receivedReqStatus; } public void setReceivedReqStatus(String receivedReqStatus) { this.receivedReqStatus = receivedReqStatus; } public String getSentReqStatus() { return sentReqStatus; } public void setSentReqStatus(String sentReqStatus) { this.sentReqStatus = sentReqStatus; } public boolean isOnline() { return isOnline; } public void setOnline(boolean online) { isOnline = online; } }
为啥这样改就好了?
- 把数据获取和视图渲染分开,Adapter只做自己该做的事,代码更清晰,也避免了异步操作带来的视图更新问题
- 当Firebase数据返回时,我们更新整个数据集,再调用
notifyDataSetChanged(),ListView会重新渲染所有可见item,确保数据和视图完全同步 - 首次启动时,即使Firebase加载慢,Adapter先显示默认数据,等数据回来后自动刷新成最新的,再也不用用户清缓存重启啦
内容的提问来源于stack exchange,提问作者Akhilesh Sharma
相关产品推荐
相关产品推荐

