简体   繁体   English

如何在使用 Kotlin 协程的 API 响应后更新 TextView?

[英]How to update a TextView after an API response with Kotlin coroutines?

I am completely new to Kotlin, Coroutines and API calls, and I am trying to make an app based on this API .我对 Kotlin、协程和 API 调用完全陌生,我正在尝试基于此 API制作一个应用程序。

My intention is to display the information of a game in my MainActivity , so I need ot fill some TextView for that purpose.我的目的是在我的MainActivity显示游戏的信息,所以我需要为此目的填充一些TextView

My API call and response system works perfectly well: the responses are OK and there are no errors, but the call is made using Kotlin's coroutines which won't let me update my UI after getting the response.我的 API 调用和响应系统运行良好:响应正常且没有错误,但调用是使用 Kotlin 的协程进行的,它不会让我在获得响应后更新我的 UI。

For the sake of simplicity, I am only attaching my MainActivity code, which is the source of the problem.为了简单起见,我只附上我的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()
    }
}

Concretely, the listAllGames() method is the problem here: the app will build successfully, if a breakpoint is added inside the if (call.isSuccessful) block, it'll show the data correctly;具体来说, listAllGames()方法就是这里的问题:app 会成功构建,如果在if (call.isSuccessful)块中添加断点,它会正确显示数据; but when running the app the display will be left blank forever.但是在运行应用程序时,显示屏将永远空白。

Thanks to all in advance!提前感谢大家!

In order to use view binding you need to pass the inflated view from the binding to the setContentView method.为了使用视图绑定,您需要将膨胀的视图从绑定传递给setContentView方法。 Otherwise you inflate the view with the binding but display the XML layout without the binding.否则,您使用绑定来扩展视图,但显示没有绑定的 XML 布局。

Check the documentation here :检查这里的文档:

private lateinit var binding: ResultProfileBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ResultProfileBinding.inflate(layoutInflater)
    val view = binding.root
    setContentView(view)
}

(Source: Android Developer documentation - " View Binding Part of Android Jetpack." ) (来源: Android 开发人员文档 - “查看 Android Jetpack 的绑定部分。”

Change your onCreate method as followed:更改您的onCreate方法如下:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    b = ActivityMainBinding.inflate(layoutInflater)
    setContentView(b.root)
    listAllGames()
}

The other answer explains your problems with your view, but you also have some issues with the coroutine.另一个答案解释了您的观点问题,但您在协程方面也有一些问题。

  • You should use lifecycleScope instead of creating a new scope.您应该使用lifecycleScope而不是创建新范围。 lifecycleScope will automatically cancel itself to avoid leaking the Activity if the activity is destroyed, such as during a screen rotation.如果 Activity 被销毁,例如在屏幕旋转期间, lifecycleScope将自动取消以避免泄漏 Activity。
  • You should use Retrofit's call.await() suspend function instead of directly blocking a thread and having to specify a dispatcher.您应该使用 Retrofit 的call.await()挂起函数,而不是直接阻塞线程并且必须指定调度程序。 This also lets you leave things on the main dispatcher and directly update UI without having to use runOnUiThread .这也使您可以将事情留在主调度程序上并直接更新 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)
            }
        }
    }

And actually, you should move API calls like this into a ViewModel so they can keep running during a screen rotation.实际上,您应该将这样的 API 调用移动到 ViewModel 中,以便它们可以在屏幕旋转期间继续运行。 If you do that, the function in the ViewModel should update a LiveData or SharedFlow instead of UI elements.如果这样做,ViewModel 中的函数应该更新 LiveData 或 SharedFlow 而不是 UI 元素。 Then your Activity can observe for changes.然后您的 Activity 可以观察变化。 If the call is started and the screen is rotated, the API call will keep running and still publish its changes to the new Activity's UI without having to restart.如果调用开始并旋转屏幕,API 调用将继续运行并仍将其更改发布到新 Activity 的 UI,而无需重新启动。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM