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

How to return DataSnapshot value as a result of a method?

提问人:Ilya S 提问时间:12/17/2017 最后编辑:Alex MamoIlya S 更新时间:3/1/2021 访问量:45822

问:

我对 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) {}
    });
}
java android firebase firebase-realtime-database

评论

2赞 Rosário Pereira Fernandes 12/17/2017
@PeterHaddad如前所述,onDataChange 方法是异步的,这意味着它将始终返回 null。请问:为什么需要返回用户名?为什么不简单地在onDataChange中使用这个名称呢?
4赞 Alex Mamo 12/17/2017
正如@PeterHaddad所说,因为方法被称为异步的,所以它不会起作用。请看我的回答。onDataChange()
3赞 Doug Stevenson 2/17/2018
阅读本文,了解 Firebase API 异步的原因:medium.com/@CodingDoug/...
1赞 Peter Haddad 2/22/2018
阅读本文以了解如何使用一个或多个 databasereference 解决异步数据库:stackoverflow.com/questions/48720701/...

答:

3赞 Rbar 12/17/2017 #1

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

  1. 在类的顶部创建一个变量
  2. 检索您的值(您已经正确地完成了)
  3. 将类中的公共变量设置为等于检索到的值

一旦提取成功,就可以对变量执行许多操作。4a 和 4b 是一些简单的示例:

4a. 编辑:作为使用示例,您可以触发在使用 (并且可以确保它不是 null) 的类中运行所需的任何其他内容yourNameVariableyourNameVariable

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 时检查它是否不为 null,因为 onDataChange 是异步的,并且在您尝试在其他地方使用它时可能尚未完成。

评论

3赞 Alex Mamo 12/17/2017
这是一个不正确的答案。当涉及到异步方法时,事情不是这样发生的。使用此答案,在使用“在整个课堂上随心所欲”时,您的变量将始终为。您只能在您的方法中使用它。就是这样。yourNameVariablenullonDataChange()
1赞 Rbar 12/18/2017
@AlexMamo,您定义接口的解决方案效果很好,但是您的评论的以下部分是不正确的:“ 永远 。对于那些寻求更轻量级解决方案(不需要定义接口)的人来说,这很有效。一旦变量被检索和设置,他们就可以触发任何需要结果的函数,并知道该变量不是(只要它首先存在于 firebase 上)。我已经更新了我的示例,以便您可以看到一个具体的用例。yourNameVariablenullnull
1赞 Alex Mamo 12/18/2017
此外,调用该方法与方法的异步行为无关。调用该方法与在方法中使用此行相同。这意味着,当方法被触发时,您正在调用该方法。Log.d(TAG, "hi there, " + yourNameVariable);onDataChange()onDataChange()
1赞 Rbar 12/19/2017
@AlexMamo,该函数只是一个示例。您可以执行许多其他不会自动触发函数的操作。再举一个简单的例子,一旦数据加载完毕,按钮就可以看到,触发按钮会对你获取的数据做一些事情。再说一次,你的例子很好,我并不是说它不应该是公认的答案。话虽如此,您的笼统陈述(即使是完整的陈述)并非适用于所有情况......(续)yourNameVariable will be be always null when using "wherever you would like throughout your class".
2赞 Alex Mamo 12/19/2017
在任何情况下,您都不能使用我在答案中已经提到的其他之外检索到的值。您现在无法返回尚未加载的内容。你的解决方案不是一个更轻的解决方案,根本行不通。尝试使用日志语句,你就会明白我的意思。
124赞 Alex Mamo 12/17/2017 #2

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

但是,不仅 Firebase Realtime Database 异步加载数据,而且几乎所有现代 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

大多数开发人员的最初反应是尝试“修复”这个问题,我个人建议不要这样做。Web 是异步的,您越早接受这一点,您就能越早了解如何使用现代 Web API 提高工作效率。asynchronous behavior

我发现为这种异步范式重新构建问题最容易。我没有说“首先获取数据,然后记录数据”,而是将问题描述为“开始获取数据。加载数据后,将其记录下来”。这意味着任何需要数据的代码都必须位于方法内部或从方法内部调用,如下所示: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()

编辑:26 2月 2021

有关详细信息,可以查看以下文章:

以及以下视频:

评论

1赞 Peter Haddad 8/1/2018
这可以使用 Future 接口和 Callable 来实现吗?developer.android.com/reference/java/util/concurrent/Future
1赞 Alex Mamo 8/1/2018
嗨,彼得!它可能会起作用。我还没有尝试过,但我会的。我不知道你是否知道这一点,但这是一篇有趣的文章
1赞 Peter Haddad 8/1/2018
是的,我以前看过它,因为它在很多地方都是作为评论写的。但是,我没有读过它,因为在阅读了这里的几个问答后,我知道异步是如何工作的。无论如何,我现在阅读了它,我想最好不要使用它,因为我们需要使用它来创建另一个线程,并且由于 api 是异步的,那么它已经在 java 中的线程中,我们还必须维护这个线程并编写更多代码。FuturenewSingleThreadExecutor()
3赞 Alex Mamo 4/18/2019
@DragonFire 您只是作为参数进行暂停,而不是在擦除数据。yourValue
2赞 Alex Mamo 10/2/2020
@willy我不能在 callback/readData() 之外执行此操作,因为该操作是异步的。您始终需要等待数据。
3赞 klaid bendio Moran 8/1/2018 #3

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

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

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

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

评论

0赞 Mahmoud Abu Alheja 1/10/2020
以及如何知道何时必须从 TextView 获取文本(该方法将是异步的)
0赞 Panos Gr 2/7/2021
除非已经被销毁。那么这只是一个疯狂的坏主意。textview
1赞 DragonFire 4/18/2019 #4

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

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

在 Async 方法中设置此变量

aPublicVariable = (String) dataSnapshot.getValue();
     

随时随地使用变量

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

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

评论

0赞 Anirudh Ganesh 6/25/2020
尝试了类似的东西,甚至在获取数据之前就传递了值,最终为 null
0赞 Eben Watts 10/16/2020
这将持续更新 1000 次,我正在使用一个涉及金钱的钱包。当我通过将新金额添加到数据库中的当前金额进行更新时。它不断重复该过程并增加资金,直到您退出应用程序,因为它是一种异步方法,因此使用数字很危险。我认为对于字符串,你不会看到效果。
2赞 Md. Asaduzzaman 12/5/2019 #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/FragmentLiveDataonChanged

getUserName().observe(this, new Observer<String>() {
    @Override
    public void onChanged(String userName) {
        //here, do whatever you want on `userName`
    }
});
9赞 Alex Mamo 2/6/2021 #6

“19.6.0”版本开始,Firebase Realtime Database 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);
    }
});

有关详细信息,可以查看以下文章:

以及以下视频:

评论

1赞 Chauhan Ajay 2/25/2021
太棒了,继续努力 ☻
1赞 Alex Mamo 2/26/2021
@ChauhanAjay 谢谢。您也可以查看本文此视频