Android-Firebase-getChildrenCount()方法的问题 [英] Android - Firebase - Issue with getChildrenCount() method
问题描述
当前,我正在构建一个使用Firebase数据库的Android应用.
Currently, I'm building an Android App which is using a Firebase Database.
在我的代码中,我试图获取Firebase数据库特定路径中的子代数.定义了Firebase特定路径的引用后,我已创建一个侦听器,以获取DataSnapshot,然后调用getChildrenCount().
In my code, I'm trying to get the number of Children in a specific path of my Firebase Database. Having defined the reference of the Firebase specific path, I have made a Listener in order to get the DataSnapshot and then to call getChildrenCount().
之后,我想在FOR-Loop中使用此结果.但是,该代码不起作用.
After that, I want to use this result in a FOR-Loop. However, the code doesn't work.
似乎首先执行FOR-Loop(我意识到,因为Log.v("NUM_OF_PRODUCTS",numOfProducts+"")
输出0代表3的指令),然后在执行侦听器,而在代码中,侦听器代码首先出现,然后是FOR-Loop代码.
It appears that it is executed first the FOR-Loop (I realised that because Log.v("NUM_OF_PRODUCTS",numOfProducts+"")
outputs 0 insteed of 3) and the Listener is executed after, while in the code, listener code comes first and FOR-Loop code next.
部分代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_product_selection);
selected_cat = getIntent().getExtras().getString("pass_selected_cat");
listView = (ListView) findViewById(R.id.listView);
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, arrayList);
listView.setAdapter(adapter);
fbdatabase = FirebaseDatabase.getInstance();
fbref_products = fbdatabase.getReference("PRODUCTS/SM_0/" + selected_cat);
//Listener for getting numOfProducts.
fbref_products.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
numOfProducts = (int) dataSnapshot.getChildrenCount();
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
//Print numOfProducts before FOR-Loop being executed.
Log.v("NUM_OF_PRODUCTS",numOfProducts+"");
//FOR-Loop
for (int i = 0; i < numOfProducts; i++) {
String str = i + "";
DatabaseReference product_ref = fbref_products.child(str);
product_ref.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
Map<String, Object> map = (Map<String, Object>) dataSnapshot.getValue();
String productName = (String) map.get("productName");
arrayList.add(productName);
adapter.notifyDataSetChanged();
}
@Override
public void onCancelled(DatabaseError databaseError) {
//textView.setText("The read failed: " + databaseError.getMessage());
}
});
}
如果我们将变量numOfProducts
设置为特定数字,则我的代码可以正常工作.
My code works fine if instead the variable numOfProducts
, we had a specific number.
有人可以帮助我吗?
推荐答案
Firebase数据库中的数据是异步加载的.在您运行for
循环时,尚未加载该循环.
Data from the Firebase Database is loaded asynchronously. By the time you run your for
loop it hasn't been loaded yet.
如果在代码中放置一些日志语句,最容易看到这一点:
It is easiest to see this if you place a few log statements in your code:
System.out.println("Before attaching listener");
fbref_products.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
System.out.println("In onDataChange");
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
System.out.println("After attaching listener");
运行此命令时,输出为:
When you run this, the output will be:
在附加侦听器之前
Before attaching listener
附加监听器后
在onDataChange
In onDataChange
这可能不是您期望的顺序.但这很好地说明了为什么在启动循环时numOfProducts
仍为0的原因:数据尚未从Firebase加载.
This is probably not the order that you expected. But it explains perfectly why numOfProducts
is still 0 when you start the loop: the data simply hasn't been loaded from Firebase yet.
几乎每个开发人员的最初反应都是我不想要这个.如何使它以正确的顺序执行?"这是很自然的反应,但是在针对Web/云API进行编程时,您将不得不压制任何反应.我还建议您在此处阅读答案:在Firebase Listener中设置Singleton属性值
In initial reaction from pretty much every developer is "I don't want this. How do I make it execute in the right order?" This is a natural response, but one that you'll have to suppress to get anywhere when programming against web/cloud APIs. I also recommend reading my answer here: Setting Singleton property value in Firebase Listener
解决方案是从首先获得产品数量,然后遍历产品"开始重新构建您的解决方案.到每当我收到产品时,我都会遍历它们".
The solution is to reframe your solution from "first I'll get the number of products and then I'll loop over the products" to "whenever I get the products, I'll loop over them".
代码为:
fbref_products.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
numOfProducts = (int) dataSnapshot.getChildrenCount();
for (int i = 0; i < numOfProducts; i++) {
String str = i + "";
DatabaseReference product_ref = fbref_products.child(str);
product_ref.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
Map<String, Object> map = (Map<String, Object>) dataSnapshot.getValue();
String productName = (String) map.get("productName");
arrayList.add(productName);
adapter.notifyDataSetChanged();
}
@Override
public void onCancelled(DatabaseError databaseError) {
//textView.setText("The read failed: " + databaseError.getMessage());
}
});
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
除了处理异步事件之外,您还可以大大简化此代码:
Aside from the whole dealing with asynchronous events, you can significantly simplify this code:
fbref_products.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot productSnapshot: dataSnapshot.getChildren()) {
String productName = productSnapshot.child("productName").getValue(String.class);
arrayList.add(productName);
adapter.notifyDataSetChanged();
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
更改:
- 这仅使用一个侦听器.不需要您的第二个侦听器,因为检索节点已经检索到该节点下 的所有数据.您可以遍历子节点.
- 这不会首先提取HashMap,而只是直接从子快照中读取数据.
- this uses only a single listener. Your second listener is not needed, since retrieving a node already retrieves all data under that node as well. You can loop over the child nodes.
- this doesn't first extract a HashMap, but simply reads the data from the child snapshot directly.
您应该考虑的另一项更改是保留一个单独的产品名称列表.现在,您检索所有产品数据以显示名称列表,这很浪费.如果仅保留产品名称的单独列表,则可以加载该列表.使用Firebase(或几乎所有NoSQL数据库)时,您会发现一个共同的主题:以在屏幕上显示数据的方式对数据库中的数据进行建模.
One more change you should consider is to keep a separate list of product names. You now retrieve all product data to show a list of names, which is wasteful. If you keep a separate list of just the product names, you can load that instead. You'll find that a common theme when using Firebase (or almost any NoSQL database): model the data in your database the way you show it on the screen.
这篇关于Android-Firebase-getChildrenCount()方法的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!