如何作为方法的结果返回 DataSnapshot 值?

Posted

技术标签:

【中文标题】如何作为方法的结果返回 DataSnapshot 值?【英文标题】:How to return DataSnapshot value as a result of a method? 【发布时间】:2020-07-15 20:38:48 【问题描述】:

我对 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) 
    );

【问题讨论】:

就像@PeterHaddad 提到的,onDataChange 方法是异步的,这意味着它将始终返回null。请问:为什么需要返回用户名?为什么不简单地在 onDataChange 中使用这个名称? 正如@PeterHaddad 所说,因为onDataChange() 方法是异步调用的,所以它不起作用。请看我的回答。 阅读本文以了解 Firebase API 为何是异步的:medium.com/@CodingDoug/… 阅读本文以了解如何使用一个或多个数据库解决异步数据库参考:***.com/questions/48720701/… 【参考方案1】:

这是异步 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,我个人建议不要这样做。网络是异步的,您越早接受这一点,您就能越早了解如何使用现代网络 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

编辑: 2021 年 2 月 26 日

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

How to read data from Firebase Realtime Database using get()?

还有以下视频:

https://youtu.be/mOB40wowo6Y

【讨论】:

这可以使用Future接口和Callable实现吗? developer.android.com/reference/java/util/concurrent/Future 嗨,彼得!它可能会起作用。我还没试过,但我会的。我不知道你是否知道,但这里有一个有趣的article。 是的,我以前看过它,因为它在很多地方都是作为评论写的。但是,我还没有读过它,因为在这里阅读了几个问答后我知道异步是如何工作的。无论如何,我现在读了它,我想最好不要使用Future,因为我们需要使用创建另一个线程的newSingleThreadExecutor(),并且由于api是异步的,所以它已经在java中的线程中,我们必须维护这个线程并编写更多代码。 @DragonFire 您只是将yourValue 作为参数传递,您不知道数据。 @willy 我无法在回调/readData() 之外执行此操作,因为操作是异步的。你总是需要等待数据。【参考方案2】:

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

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

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

public interface FirebaseCallback 
    void onResponse(String name);

还有一个将 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);
    
);

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

How to read data from Firebase Realtime Database using get()?

还有以下视频:

https://youtu.be/mOB40wowo6Y

【讨论】:

太棒了,继续努力☻ @ChauhanAjay 谢谢。你也可以看看这个article 和这个video。【参考方案3】:

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

    在类的顶部创建一个变量 检索您的价值(您已经完成了大部分正确的操作) 将类中的公共变量设置为检索到的值

一旦你的 fetch 成功,你可以用这个变量做很多事情。 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 是异步的,并且在您尝试在其他地方使用它时可能还没有完成。 p>

【讨论】:

这是一个错误的答案。当涉及到异步方法时,情况并非如此。使用此答案时,您的变量 yourNameVariable 将始终为 null,当您使用“在整个班级中您想要的任何地方”时。您只能在 onDataChange() 方法中使用它。就是这样。 @AlexMamo,您定义接口的解决方案效果很好,但您评论的以下部分不正确:“yourNameVariable 始终为null。”对于那些寻找更轻量级解决方案(不需要定义接口)的人来说,这很有效。一旦变量被检索和设置,他们就可以触发任何需要结果的函数,并且知道该变量不是null(只要它首先存在于firebase上)。我已经更新了我的示例,以便您可以看到具体的用例。 此外,调用该方法与方法的异步行为无关。调用该方法与在 onDataChange() 方法中使用这一行 Log.d(TAG, "hi there, " + yourNameVariable); 相同。这意味着当onDataChange() 方法被触发时,您正在调用该方法。 @AlexMamo,这个函数只是一个例子。您可以做许多其他不会自动触发功能的事情。再举一个简单的例子,一旦数据加载完毕,一个按钮就会可见,并且触发该按钮将对您获取的数据执行一些操作。同样,您的示例很好,我并不是说它不应该是公认的答案。话虽如此,您对yourNameVariable will be be always null when using "wherever you would like throughout your class". 的一揽子声明(甚至是完整声明)并非适用于所有情况......(续) 没有任何情况可以使用在我的回答中已经提到的其他值之外检索到的值。您现在无法退回尚未加载的内容。您的解决方案不是一个更轻的解决方案,根本行不通。尝试使用日志语句,您就会明白我的意思。【参考方案4】:

这是一个疯狂的想法,在 onDataChange 里面,把它放在一个可见性消失的 TextView 里面 textview.setVisiblity(Gone) 什么的, 然后做类似的事情

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

然后用textview.getText().toString()获取它

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

【讨论】:

以及如何知道何时必须从 TextView 获取文本(该方法将是异步的) 除非textview 已经被销毁。那么这只是一个疯狂的坏主意。【参考方案5】:

使用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`
    
);

【讨论】:

【参考方案6】:

这不是解决方案,只是一种在代码组织方法之外访问数据的方法。

// 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);


实现结果的另一种方式(但不一定是解决方案)

声明一个公共变量

public static String aPublicVariable;

在异步方法中设置这个变量

aPublicVariable = (String) dataSnapshot.getValue();
     

在任何地方使用变量

Log.d(TAG, "Not Elegant: " + aPublicVariable);

在第二种方法中,如果异步调用不长,它几乎可以一直工作。

【讨论】:

尝试了类似的方法,甚至在获取数据之前就传递了值并最终为 null 这将持续更新1000次,我正在使用涉及金钱的钱包。当我通过向数据库中的当前金额添加新金额进行更新时。它不断重复这个过程并增加资金,直到你退出应用程序,因为它是一种异步方法,所以使用数字很危险。我认为对于字符串,你不会看到效果。

以上是关于如何作为方法的结果返回 DataSnapshot 值?的主要内容,如果未能解决你的问题,请参考以下文章

如何作为方法的结果返回 DataSnapshot 值?

从 Firebase 为每个特定帖子检索特定数据的比 datasnapshot 更好的方法?返回的位置错误

Firebase dataSnapshot 如何访问值

如何在 Android Studio 的 datasnapshot/recyclerview 中创建条件?

如何作为方法的结果返回 DocumentSnapShot?

Flutter:未为 DataSnapshot 类定义 forEach 方法