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)
將打?。?/p>
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)
}
})
}
添加回答
舉報