如何通过方法返回DataSnapshot值?

时间:2017-12-16 16:25:28

标签: java android firebase firebase-realtime-database

我对Java没有多少经验。我不确定这个问题是不是很愚蠢,但是我需要从Firebase实时数据库中获取用户名,并且由于此方法而返回此名称。所以,我想出了如何获得这个值,但我不明白如何返回它作为这个方法的结果。最好的方法是什么?

private String getUserName(String uid) {
    databaseReference.child(String.format("users/%s/name", uid))
            .addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            // How to return this value?
            dataSnapshot.getValue(String.class);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {}
    });
}

6 个答案:

答案 0 :(得分:61)

这是异步Web API的经典问题。您现在无法返回尚未加载的内容。换句话说,您不能简单地创建全局变量并在onDataChange()方法之外使用它,因为它始终是null。发生这种情况是因为onDataChange()方法被称为异步。根据您的连接速度和状态,在数据可用之前可能需要几百毫秒到几秒钟。

但不仅Firebase实时数据库异步加载数据,几乎所有现代其他Web API都会这样做,因为它可能需要一些时间。因此,不是等待数据(这可能导致用户无响应的应用程序对话框),而是在辅助线程上加载数据时,主应用程序代码会继续。然后,当数据可用时,将调用onDataChange()方法并可以使用该数据。换句话说,在调用onDataChange()方法时,您的数据尚未加载。

让我们举一个例子,在代码中放置一些日志语句,以便更清楚地了解正在发生的事情。

private String getUserName(String uid) {
    Log.d("TAG", "Before attaching the listener!");
    databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            // How to return this value?
            dataSnapshot.getValue(String.class);
            Log.d("TAG", "Inside onDataChange() method!");
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {}
    });
    Log.d("TAG", "After attaching the listener!");
}

如果我们运行此代码,输出将是:

  

在连接听众之前!

     

附加听众后!

     

在onDataChange()方法中!

这可能不是您所期望的,但它会在返回时准确解释您的数据为null的原因。

大多数开发人员的初始响应是尝试“修复”此asynchronous behavior,我个人建议不要这样做。网络是异步的,越早接受,您就越早了解如何通过现代Web API提高工作效率。

我发现为这个异步范例重新构建问题最容易。我没有说“首先获取数据,然后记录它”,而是将问题框定为“开始获取数据。加载数据时,记录它”。这意味着任何需要数据的代码必须在onDataChange()方法内部或从内部调用,如下所示:

databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // How to return this value?
        if(dataSnapshot != null) {
            System.out.println(dataSnapshot.getValue(String.class));
        }
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {}
});

如果你想在外面使用它,还有另一种方法。您需要创建自己的回调以等待Firebase返回数据。要实现这一点,首先需要创建一个interface,如下所示:

public interface MyCallback {
    void onCallback(String value);
}

然后,您需要创建一个实际从数据库获取数据的方法。此方法应如下所示:

public void readData(MyCallback myCallback) {
    databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            String value = dataSnapshot.getValue(String.class);
            myCallback.onCallback(value);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {}
    });
}

最后,只需调用readData()方法并将MyCallback接口的实例作为参数传递给任何需要的地方,如下所示:

readData(new MyCallback() {
    @Override
    public void onCallback(String value) {
        Log.d("TAG", value);
    }
});

这是您可以在onDataChange()方法之外使用该值的唯一方法。有关详细信息,您还可以查看 video

答案 1 :(得分:5)

"19.6.0" 版本开始,Firebase 实时数据库 SDK 包含一个名为 get() 的新方法,该方法可以在 DatabaseReference 或 Query 对象上调用:

<块引用>

添加了 DatabaseReference#get() 和 Query#get(),即使本地缓存中有旧数据可用,它们也会从服务器返回数据。

我们已经知道,Firebase API 是异步的。所以我们需要为此创建一个回调。首先,让我们创建一个接口:

public interface FirebaseCallback {
    void onResponse(String name);
}

还有一个以 tye FirebaseCallback 的对象作为参数的方法:

public void readFirebaseName(FirebaseCallback callback) {
    DatabaseReference uidRef = databaseReference.child(String.format("users/%s/name", uid))
    uidRef.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
        @Override
        public void onComplete(@NonNull Task<DataSnapshot> task) {
            if (task.isSuccessful()) {
                String name = task.getResult().getValue(String.class);
                callback.onResponse(name);
            } else {
                Log.d(TAG, task.getException().getMessage());
            }
        }
    });
}

现在,要读取数据,您只需调用上述方法,将 FirebaseCallback 类型的对象作为参数传递:

readFirebaseName(new FirebaseCallback() {
    @Override
    public void onResponse(String name) {
        Log.d("TAG", name);
    }
});

有关更多信息,您可以查看以下文章:

还有以下视频:

答案 2 :(得分:3)

这是一个疯狂的主意,在onDataChange内,将其放在TextView中,可见性消失了 textview.setVisiblity(Gone)之类的, 然后做类似

textview.setText(dataSnapshot.getValue(String.class))

然后再用textview.getText().toString()

只是一个疯狂的简单想法。

答案 3 :(得分:2)

我相信我明白你在问什么。虽然你说你想从fetch方法“返回”它(本身),但是你可能只想说你实际上只想在你的fetch完成后使用检索到的值。如果是这样,这就是你需要做的事情:

  1. 在班级顶部创建一个变量
  2. 检索您的价值(您最常做的事情)
  3. 将类中的公共变量设置为等于检索到的值
  4. 一旦你的获取成功,你可以用变量做很多事情。图4a和4b是一些简单的例子:

    4A。 修改 作为使用示例,您可以在使用yourNameVariable的类中触发您需要运行的任何其他内容(并且您可以确定它yourNameVariable不为空)

    4b中。 修改 作为使用示例,您可以在由按钮的onClickListener触发的函数中使用该变量。

    试试这个。

    // 1. Create a variable at the top of your class
    private String yourNameVariable;
    
    // 2. Retrieve your value (which you have done mostly correctly)
    private void getUserName(String uid) {
        databaseReference.child(String.format("users/%s/name", uid))
                .addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                // 3. Set the public variable in your class equal to value retrieved
                yourNameVariable = dataSnapshot.getValue(String.class);
                // 4a. EDIT: now that your fetch succeeded, you can trigger whatever else you need to run in your class that uses `yourNameVariable`, and you can be sure `yourNameVariable` is not null.
                sayHiToMe();
            }
    
            @Override
            public void onCancelled(DatabaseError databaseError) {}
        });
    }
    
    // (part of step 4a)
    public void sayHiToMe() {
      Log.d(TAG, "hi there, " + yourNameVariable);
    }
    
    // 4b. use the variable in a function triggered by the onClickListener of a button.
    public void helloButtonWasPressed() {
      if (yourNameVariable != null) {
        Log.d(TAG, "hi there, " + yourNameVariable);
      }
    }
    

    然后,您可以在课堂上的任何地方使用yourNameVariable

    注意:请确保在使用yourNameVariable时检查onDataChange是否为空,因为/bin/ls -ls | tail -n +2 | awk '{print $10,$6,"BYTES"}'是异步的,并且在您尝试在其他地方使用时可能尚未完成。

答案 4 :(得分:0)

这是有效的,您可以通过将值作为参数传递给另一个函数来使用onDataChange之外的datasnapshot中的值,请在下面查看我的答案是否有效,并且在应用程序的各处使用它。 / p>

// Get Your Value
private void getValue() {

    fbDbRefRoot.child("fbValue").addListenerForSingleValueEvent(new ValueEventListener() {

        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

            String yourValue = (String) dataSnapshot.getValue();
            useValue(yourValue);

        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });

}

// Use Your Value
private void useValue(String yourValue) {

    Log.d(TAG, "countryNameCode: " + yourValue);

}

我的回答和Rbar回答的主​​要区别是,我将值作为参数传递给函数,然后使用它。

答案 5 :(得分:0)

使用LiveData作为返回类型,并观察其值的变化以执行所需的操作。

private MutableLiveData<String> userNameMutableLiveData = new MutableLiveData<>();

public MutableLiveData<String> getUserName(String uid) {

    databaseReference.child(String.format("users/%s/name", uid))
            .addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            // How to return this value?
            String userName = dataSnapshot.getValue(String.class);
            userNameMutableLiveData.setValue(userName);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {}
    });

    return userNameMutableLiveData;
}

然后从您的Activity/Fragment观察LiveData,然后在onChanged内进行所需的操作。

getUserName().observe(this, new Observer<String>() {
    @Override
    public void onChanged(String userName) {
        //here, do whatever you want on `userName`
    }
});