Android Firebase嵌套addValueEventListener时变量访问错误求助
嘿,这个问题我之前做Firebase Android开发时也碰到过,咱们一步步拆解解决它~
先搞懂错误原因
你遇到的报错:
Error: (157, 33) error: local variable UserName is accessed from within inner class; needs to be declared final
本质是Java的语法限制:内部类(这里就是嵌套的addListenerForSingleValueEvent里的ValueEventListener)访问外部方法的局部变量时,这个变量必须是final或者「effectively final」(也就是初始化后不再被修改)。但你在内部类里给UserName做了赋值操作UserName= map.get("name").toString();,打破了这个要求,所以编译器直接报错了。
另外还有个隐藏的大坑:Firebase的数据库操作是异步执行的!你现在的代码里,嵌套监听的onDataChange还没拿到用户名称,就已经执行了HistoryObject obj =new HistoryObject(...)并更新适配器了,这时候UserName还是空字符串,根本拿不到真实的用户名。这个问题必须一起解决。
具体解决方案
方案1:用可变容器包装变量(快速修复)
因为String是不可变对象,没法在内部类里修改,我们可以用一个可变容器来包装它,比如AtomicReference:
private void UserPostInformation(final String key) { DatabaseReference historyDatabase = FirebaseDatabase.getInstance().getReference().child("ThePostting").child(key); historyDatabase.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { if (dataSnapshot.exists()) { String IdHistory = dataSnapshot.getKey(); Long time = 0L; String foodname11 = ""; String imagefood1 = ""; String s = null; // 用AtomicReference包装字符串,替代原来的String UserName AtomicReference<String> userNameRef = new AtomicReference<>(""); for (DataSnapshot child : dataSnapshot.getChildren()) { if (child.getKey().equals("time")) { time = Long.valueOf(child.getValue().toString()); } if (child.getKey().equals("FoodName")) { foodname11 = child.getValue().toString(); } if (child.getKey().equals("profileImageUrl")) { imagefood1 = child.getValue().toString(); } if (child.getKey().equals("UserID")) { s = child.getValue().toString(); } } UserIDGetInfo = s; DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference().child("Users").child(UserIDGetInfo); databaseReference.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { if (dataSnapshot.exists()) { Map<String,Object> map = (Map<String,Object>) dataSnapshot.getValue(); if (map.get("name")!=null) { // 修改容器里的值 userNameRef.set(map.get("name").toString()); } // 把创建对象和更新适配器的代码移到这里!确保拿到用户名后再执行 HistoryObject obj =new HistoryObject(IdHistory , getDate(time),foodname11 , imagefood1 , userNameRef.get()); resultHistory.add(obj); mhistoryAdapter.notifyDataSetChanged(); } } @Override public void onCancelled(DatabaseError databaseError) { } }); } } @Override public void onCancelled(DatabaseError databaseError) { } }); }
方案2:把变量改成类成员变量
如果这个用户名变量不需要局限在方法内部,可以把它声明为类的成员变量,这样内部类就能直接访问修改:
// 在类的成员区域声明变量 private String userName; private void UserPostInformation(final String key) { DatabaseReference historyDatabase = FirebaseDatabase.getInstance().getReference().child("ThePostting").child(key); historyDatabase.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { if (dataSnapshot.exists()) { String IdHistory = dataSnapshot.getKey(); Long time = 0L; String foodname11 = ""; String imagefood1 = ""; String s = null; // 去掉原来的局部变量String UserName = ""; for (DataSnapshot child : dataSnapshot.getChildren()) { // 省略原有赋值逻辑... } UserIDGetInfo = s; DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference().child("Users").child(UserIDGetInfo); databaseReference.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { if (dataSnapshot.exists()) { Map<String,Object> map = (Map<String,Object>) dataSnapshot.getValue(); if (map.get("name")!=null) { userName = map.get("name").toString(); } // 同样要把创建对象的代码移到这里 HistoryObject obj =new HistoryObject(IdHistory , getDate(time),foodname11 , imagefood1 , userName); resultHistory.add(obj); mhistoryAdapter.notifyDataSetChanged(); } } @Override public void onCancelled(DatabaseError databaseError) { } }); } } @Override public void onCancelled(DatabaseError databaseError) { } }); }
方案3:用回调重构代码(更优雅的长期方案)
如果业务逻辑复杂,建议把获取用户信息的逻辑抽出来,用自定义回调处理异步结果,代码结构会更清晰:
// 定义回调接口 interface OnUserInfoLoadedListener { void onUserInfoLoaded(String userName); } // 抽离获取用户信息的方法 private void getUserInfo(String userId, final OnUserInfoLoadedListener listener) { DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference().child("Users").child(userId); databaseReference.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { String userName = ""; if (dataSnapshot.exists()) { Map<String,Object> map = (Map<String,Object>) dataSnapshot.getValue(); if (map.get("name") != null) { userName = map.get("name").toString(); } } listener.onUserInfoLoaded(userName); } @Override public void onCancelled(DatabaseError databaseError) { listener.onUserInfoLoaded(""); } }); } // 原方法调用回调 private void UserPostInformation(final String key) { DatabaseReference historyDatabase = FirebaseDatabase.getInstance().getReference().child("ThePostting").child(key); historyDatabase.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { if (dataSnapshot.exists()) { String IdHistory = dataSnapshot.getKey(); Long time = 0L; String foodname11 = ""; String imagefood1 = ""; String userId = null; // 用switch替代多个if,代码更简洁 for (DataSnapshot child : dataSnapshot.getChildren()) { switch (child.getKey()) { case "time": time = Long.valueOf(child.getValue().toString()); break; case "FoodName": foodname11 = child.getValue().toString(); break; case "profileImageUrl": imagefood1 = child.getValue().toString(); break; case "UserID": userId = child.getValue().toString(); break; } } if (userId != null) { getUserInfo(userId, new OnUserInfoLoadedListener() { @Override public void onUserInfoLoaded(String userName) { HistoryObject obj =new HistoryObject(IdHistory , getDate(time),foodname11 , imagefood1 , userName); resultHistory.add(obj); mhistoryAdapter.notifyDataSetChanged(); } }); } } } @Override public void onCancelled(DatabaseError databaseError) { } }); }
小建议
Java编码规范里变量名一般用小驼峰(比如userName而不是UserName),建议调整一下,避免和类名混淆哦~
内容的提问来源于stack exchange,提问作者AyoubDesigner01

