Android-Firebase-getChildrenCount()方法的问题 [英] Android - Firebase - Issue with getChildrenCount() method

查看:63
本文介绍了Android-Firebase-getChildrenCount()方法的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当前,我正在构建一个使用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) {
    }
});

更改:

  1. 这仅使用一个侦听器.不需要您的第二个侦听器,因为检索节点已经检索到该节点下 的所有数据.您可以遍历子节点.
  2. 这不会首先提取HashMap,而只是直接从子快照中读取数据.
  1. 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.
  2. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆