我試過搜索 stackoverflow,但找不到我的問題的答案。單擊搜索按鈕時,我希望應用程式顯示來自 API 的資料。我遇到的問題是它需要點擊 2 次搜索按鈕才能顯示資料。第一次單擊顯示“空”,第二次單擊正確顯示所有資料。我究竟做錯了什么?我需要更改什么才能在第一次點擊時正確處理?提前致謝!
配對片段
package com.example.winepairing.view.fragments
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.activityViewModels
import com.example.winepairing.databinding.FragmentPairingBinding
import com.example.winepairing.utils.hideKeyboard
import com.example.winepairing.viewmodel.PairingsViewModel
class PairingFragment : Fragment() {
private var _binding: FragmentPairingBinding? = null
private val binding get() = _binding!!
private val viewModel: PairingsViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentPairingBinding.inflate(inflater, container, false)
val view = binding.root
val toolbar = binding.toolbar
(activity as AppCompatActivity).setSupportActionBar(toolbar)
binding.searchBtn.setOnClickListener {
hideKeyboard()
if (binding.userItem.text.isNullOrEmpty()) {
Toast.makeText(this@PairingFragment.requireActivity(),
"Please enter a food, entree, or cuisine",
Toast.LENGTH_SHORT).show()
} else {
val foodItem = binding.userItem.text.toString()
getWinePairing(foodItem)
pairedWinesList()
pairingInfo()
}
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun pairedWinesList() {
val pairedWines = viewModel.apiResponse.value?.pairedWines
var content = ""
if (pairedWines != null) {
for (i in 0 until pairedWines.size) {
//Append all the values to a string
content = pairedWines.get(i)
content = "\n"
}
}
binding.pairingWines.setText(content)
}
private fun pairingInfo() {
val pairingInfo = viewModel.apiResponse.value?.pairingText.toString()
binding.pairingInfo.setText(pairingInfo)
}
private fun getWinePairing(foodItem: String) {
viewModel.getWinePairings(foodItem.lowercase())
}
}
非常抱歉!!!這是視圖模型
package com.example.winepairing.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.winepairing.BuildConfig
import com.example.winepairing.model.data.Wine
import com.example.winepairing.model.network.WineApi
import kotlinx.coroutines.launch
const val CLIENT_ID = BuildConfig.SPOONACULAR_ACCESS_KEY
class PairingsViewModel: ViewModel() {
private val _apiResponse = MutableLiveData<Wine>()
val apiResponse: LiveData<Wine> = _apiResponse
fun getWinePairings(food: String) {
viewModelScope.launch {
_apiResponse.value = WineApi.retrofitService.getWinePairing(food, CLIENT_ID)
}
}
}
uj5u.com熱心網友回復:
盡管您沒有分享您的 ViewModel 代碼,但我猜測您的 ViewModelgetWinePairings()函式從 API 異步檢索資料,然后更新apiResponse使用回傳值呼叫的 LiveData 。由于 API 回應在回傳之前需要一些時間,因此apiResponse當您pairedWinesList()從單擊偵聽器呼叫 Fragment 的函式時,您的LiveData 仍然是空的。
提示,每當您在.value管理它的 ViewModel 之外使用LiveData 時,您都可能做錯了什么。LiveData 的重點是在資料到達時對其做出反應,因此您應該呼叫observe()它而不是嘗試.value同步讀取它。
此問題中有關異步呼叫的更多資訊。
uj5u.com熱心網友回復:
您尚未發布用于從 API 獲取資料的實際代碼(可能在 中viewModel#getWinePairings),但猜測它在您的按鈕單擊偵聽器中是這樣的:
- 你叫
getWinePairing-這揭開序幕一個異步呼叫上,將最終完成并的資料集viewModel.apiResponse,在將來某個時候。它的初始值為null - 你呼叫
pairedWinesListwhich 參考了的當前值apiResponse- 這將是空的,直到 API 呼叫在其上設定一個值。由于您基本上是在獲取最近完成的搜索結果,如果您更改搜索資料,那么您最終將顯示上一次搜索的結果,而 API 呼叫在后臺運行并apiResponse稍后更新 - 您呼叫的
pairingInfo()是與上面相同的,在 API 呼叫回傳新結果之前,您正在查看一個陳舊的值
這里的問題是你這樣做需要一段時間才能完全異步呼叫,但是你想通過讀取電流立即顯示結果value你的apiResponse LiveData。您不應該這樣做,您應該使用更具反應性observe的 LiveData設計,并在發生某些事情時(即,當您獲得新結果時)更新您的 UI:
// in onCreateView, set everything up
viewModel.apiResponse.observe(viewLifeCycleOwner) { response ->
// this is called when a new 'response' comes in, so you can
// react to that by updating your UI as appropriate
binding.pairingInfo.setText(response.pairingInfo.toString())
// this is a way you can combine all your wine strings FYI
val wines = response.pairedWines?.joinToString(separator="\n") ?: ""
binding.pairingWines.setText(wines)
}
binding.searchBtn.setOnClickListener {
...
} else {
val foodItem = binding.userItem.text.toString()
// kick off the API request - we don't display anything here, the observing
// function above handles that when we get the results back
getWinePairing(foodItem)
}
}
希望這是有道理的 - 按鈕單擊偵聽器只是啟動異步獲取操作(可能是網路呼叫,或緩慢的資料庫呼叫,或者不會阻塞執行緒的快速記憶體中獲取 - 片段不需要了解詳細資訊!)并且該observe函式處理在 UI 中顯示新狀態,無論何時到達。
優點是您將所有內容分開 - 視圖模型處理狀態,UI 僅處理諸如點擊(更新視圖模型)和顯示新狀態(對視圖模型中的更改做出反應)之類的事情。這樣你也可以做一些事情,比如讓 VM 保存自己的狀態,當它初始化時,UI 只會對這個變化做出反應并自動顯示它。它不需要知道是什么導致了這種變化,你知道嗎?
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/395424.html
上一篇:配置自動生成的按鈕以顯示不同的值
