我对Kotlin,协程和API调用完全陌生,我正试图基于此API制作一个应用程序。
我的目的是在我的MainActivity
中显示游戏的信息,所以我需要填充一些TextView
来达到这个目的。
我的API调用和响应系统工作得很好:响应是OK的,没有错误,但是调用是使用Kotlin的协程,这将不让我更新我的UI后得到响应。
为了简单起见,我只附加了MainActivity代码,这是问题的根源。
class MainActivity : AppCompatActivity() {
private lateinit var b: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
b = ActivityMainBinding.inflate(layoutInflater)
setContentView(R.layout.activity_main)
listAllGames()
}
private fun listAllGames() {
CoroutineScope(Dispatchers.IO).launch {
val call = getRetrofit().create(APIService::class.java).listAllGames("")
val games = call.body()
runOnUiThread {
if (call.isSuccessful) {
b.gameTitle.text = games?.get(0)?.title ?: "Dummy"
b.gameDesc.text = games?.get(0)?.short_description ?: "Dummy"
b.gameGenre.text = games?.get(0)?.genre ?: "Dummy"
}
else {
Toast.makeText(applicationContext, "ERROR", Toast.LENGTH_SHORT).show()
Log.d("mydebug", "call unsuccessful")
}
}
}
}
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://www.freetogame.com/api/games/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
具体来说,listAllGames()
方法是这里的问题:应用程序将成功构建,如果在if (call.isSuccessful)
块内添加一个断点,它将正确显示数据;但是当运行应用程序时,显示将永远保持空白。
提前感谢大家!
为了使用视图绑定,您需要将膨胀的视图从绑定传递给setContentView
方法。否则,您将使用绑定膨胀视图,但显示没有绑定的XML布局。
查看这里的文档:
private lateinit var binding: ResultProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
(来源:Android Developer文档- "Android Jetpack的View Binding部分。")
改变你的onCreate
方法如下:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
b = ActivityMainBinding.inflate(layoutInflater)
setContentView(b.root)
listAllGames()
}
另一个答案解释了你的视图的问题,但是你在协程上也有一些问题。
- 您应该使用
lifecycleScope
而不是创建一个新的作用域。lifecycleScope
将自动取消自己,以避免泄漏活动,如果活动被破坏,如在屏幕旋转。 - 你应该使用Retrofit的
call.await()
suspend函数,而不是直接阻塞线程,并且必须指定一个调度程序。这也可以让你把事情留在主调度程序上,直接更新UI,而不必使用runOnUiThread
。
private fun listAllGames() {
lifecycleScope.launch {
val call = getRetrofit().create(APIService::class.java).listAllGames("")
try {
val games = call.await()
b.gameTitle.text = games?.get(0)?.title ?: "Dummy"
b.gameDesc.text = games?.get(0)?.short_description ?: "Dummy"
b.gameGenre.text = games?.get(0)?.genre ?: "Dummy"
} catch (e: Exception) {
Toast.makeText(applicationContext, "ERROR", Toast.LENGTH_SHORT).show()
Log.d("mydebug", "call unsuccessful", e)
}
}
}
实际上,你应该把这样的API调用移动到ViewModel中,这样它们可以在屏幕旋转时继续运行。如果这样做,ViewModel中的函数应该更新LiveData或SharedFlow,而不是UI元素。然后你的活动可以观察变化。如果调用被启动并且屏幕被旋转,API调用将继续运行并且仍然将其更改发布到新Activity的UI而无需重新启动。