3 回答
TA贡献2051条经验 获得超10个赞
监听器是异步的,如果你把 println 语句放在username =
行下面,它就会打印。
事实上,继续做吧;观察时间戳;哪个先打印?空的还是回调里面的?
var正在被回调修改,但println
在 Firebase 发出其值之前很久(在计算机时代,即)首先执行。
此外,我会颠倒行的顺序mDatabase
。您本质上是在请求一个值,然后听取结果;结果可能已经发出。您应该先添加侦听器,然后再请求数据。
更新:如果我需要另一个回调的值怎么办?
欢迎来到异步编程的世界:-)
你描述的是一组独立的异步操作。您需要值 A 和值 B,但是在获得值 A 之前您无法获得值 B。两者都是异步的并且需要时间,但是您在主线程上没有时间,或者更确切地说,您有 ~16ms计算、测量和绘制屏幕,以便操作系统可以保持每秒 60 帧的速度。这不是很多时间,也是异步编程存在的部分原因!
简而言之,您想要的是一个对象的实例,一旦操作完成就可以调用它。
在常规同步函数中,每个语句都在另一个语句之后执行,并且在前一个语句未完成之前不会执行任何语句;因此,所有语句都是阻塞语句。
例如:
var times = 2
var message = "Hello"
var world = "World"
println("The message is $message $times")
println(world)
将打印:
The message is Hello 2
World
这是因为执行点会从一行走到另一行,等待上一行执行。如果一个操作需要时间,线程将被阻塞(无法执行任何其他操作)直到该操作完成并且执行点可以移动到下一条指令。
可以想象,iOS 和 Android(以及 Windows、macOS、Linux 等)中的主线程无法被阻止,否则操作系统将无法响应您的触摸和其他发生的事情(例如,手机,如果用户界面没有响应并且您无法点击“接听”),则无法处理来电。
这就是为什么我们使用其他“线程”来卸载不是超快的东西。这伴随着思维方式的改变以及正确的计划,因为现在事情变得更加复杂了。
让我们看一个简单的例子(一些伪代码,所以请原谅任何明显的错误,这只是为了说明这一点,而不是写一个解决方案)。
fun main() {
var hello = "Hello"
var message = thisTakesTime()
println("The message is $hello $message")
println(hello)
}
fun thisTakesTime(): String {
// do something that takes 1 second (1000 ms)
Thread.sleep(1000)
return "World"
}
这将打印
The message is Hello World
Hello
如您所见,除了整整一秒钟,主线程没有响应外,没有任何变化。例如,如果您要在 Android 上运行它,它会工作,但您的应用程序在 Thread.sleep 期间不会有一秒钟的响应。一秒快,试试10秒;在决定需要 ANR(应用程序未响应)对话框之前,这超过了 Android 操作系统对主线程无响应的 5 秒限制;这就是臭名昭著的“看起来 XXX 应用程序没有响应,等待或关闭”。
你能做什么?
最初,如果你有太多的回调(其中回调 A 在回调 B 完成之前无法执行,而回调 B 在回调 C 完成之前无法执行),并且你开始像那样嵌套它们,你最终会陷入臭名昭著的回调地狱(在 Javascript中) ,但适用于任何语言/平台)。
基本上跟踪所有这些异步回调并确保在响应到来时,您的下一个回调已准备就绪,等等是一件痛苦的事情,并且它会引入指数级的复杂性,例如,如果回调 C 在中间失败,现在您必须让回调 B 知道 C 失败了,因此它也必须失败,这反过来又必须让回调 A(原来的!)知道 B 失败了,因此 A 必须对此做些什么,A 是否需要知道 B 因为 C 而失败了吗?还是 A 只关心 B 和 B,而 B 失败背后的原因无关紧要?
好吧,正如您所看到的,即使谈论这个也会变得复杂和混乱,我什至没有涵盖其他同样复杂的可能场景。
我在这里想说的并不是说你不应该使用回调;而是说你不应该使用回调。而是您必须仔细计划何时何地使用它们。
Kotlin 有一些替代方案可以通过使用协程来减少/消除回调地狱,但这些是一个中等高级的主题,它还需要对组件和部件的设计方式进行根本性的改变。
总而言之,对于您的用例,请记住 OOP 的黄金法则:制作做很少事情的小型具体类,并将它们做好。if ()
如果您需要在各处开始添加太多,那么您很可能会在各处混合业务逻辑、随机决策和“whatabout”案例。
假设您有一个处理位置数据并将其上传到服务器的类。
您可能会想:
在 Activity/Fragment(或 ViewModel)中编写所有代码;很快就变得一团糟。
使用静态方法(或单例模式)创建 LocationUtils;已经一团糟,但也很难测试和模拟。如果您需要不止一种类型的处理怎么办?或者如果你想将它们存储在数据库中,你是否要添加更多的静态方法?
创建一个小的 LocationProcessor 类,它接收两个点(纬度/经度)在一个小函数中进行处理,并返回处理后的数据,然后创建另一个名为 LocationUploader 的类,它从处理器接收干净的输入,并将其上传到服务器. 这些类都不应该考虑“如果我没有权限怎么办,如果用户关闭位置怎么办”等等。这些问题超出了一个类的责任范围,该类的目的是处理位置坐标,别无其他。应该有其他类负责。请记住,小班级、小职责== 在单个文件中无需担心。
结论?
好吧,此时有更好的答案可以为您提供所需内容的复制粘贴版本;我相信你今天必须从这堵文字墙中拿出的概念是,为了编写现代的、可测试的、简单的功能代码,你计划事情的方式必须发生变化。
长话短说:当事情不是同步的时候,你需要保持一些东西(一个对象)准备好被回调(因此名称回调),监听(或观察)(因此我们称它们为监听器或观察器),发射某物(通常称为 Observable,因为它可以被“观察”)。
祝你好运!
TA贡献1806条经验 获得超8个赞
正如 Martin 所说,这是一个异步操作,您应该在异步过程完成后处理文本输出:
mDatabase.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
TODO("not implemented")
}
override fun onDataChange(snapshot: DataSnapshot) {
userName = snapshot.child(uid).child("name").getValue().toString()
println(userName) //--> Asynchronous request has ended, show the name
}
})
TA贡献1799条经验 获得超6个赞
是的,侦听器是异步的,只有在 onDataChange 方法中打印变量时它才会起作用。
但是,您可以使用回调策略来等待 Firebase 返回数据。是这样的:
interface MyCallback {
fun onCallback(value: String )
}
fun readData(myCallback: MyCallback){
mDatabase.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
userName = snapshot.child(uid).child("name").getValue().toString()
myCallback.onCallback(value)
}
})
}
fun test(){
readData(object: MyCallback {
override fun onCallback(value : String) {
println(value)
}
})
}
添加回答
举报