Home > Enterprise >  How to set textView for a fragment with Kotlin?
How to set textView for a fragment with Kotlin?

Time:11-28

Using the "BottomNaviation" template in Android Studio for my project, I'm faced with the problem of how to update a textView for a fragment.

The "BottomNaviation" template in AndroidStudio has a HomeFragment.kt, I am able to set the textView from:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... }

However, if I start a CoroutineScope() as such:

CoroutineScope(Dispatchers.IO).launch {
    test_function()
}

I am unable to update textView from within test_function().

In particular it fails here:

private suspend fun client(address : String, port: Int) {
    val connection: Socket

    try {
        connection = Socket(address, port)
    }
    catch(e: Exception) {
        textView.text = "Error: Socket exception: "   e
        return
    }
    val writer = connection.getOutputStream()
    writer.write(1)
    val reader = Scanner(connection.getInputStream())
    textView.text = "test string"
    ...
}

It fails at the line:

textView.text = "test string"

by crashing.

Am I supposed to use findViewById() from within test_function() to obtain a handle (?) or something to my textView? Even if this is the correct solution, I'm stuck at the following error:

Unresolved reference: findViewById

What do I need to do in order to be able to update a textView from within the function called by the CoroutineScope?

CodePudding user response:

You didn’t share the stack trace, but I assume the problem is that you were updating the TextView from a thread other than the main thread, which is not allowed.

By convention, a suspend function should always delegate work to the appropriate dispatcher(s) internally. So if there are any blocking function calls in your code above, you should wraps them in withContext(Dispatchers.IO) { } and likewise you should wrap the parts of your code that modify views with withContext(Dispatchers.Main) { }. For example:

private suspend fun client(address : String, port: Int) = withContext(Dispatchers.IO) {
    val connection: Socket

    try {
        connection = Socket(address, port)
    }
    catch(e: Exception) {
        withContext(Dispatchers.Main) { textView.text = "Error: Socket exception: "   e }
        return
    }
    val writer = connection.getOutputStream()
    writer.write(1)
    val reader = Scanner(connection.getInputStream())
    withContext(Dispatchers.Main) {  textView.text = "test string" }
    ...
}

When you launch your coroutine, if it is merely calling suspend functions and non-blocking functions, there’s no need to specify a dispatcher there, assuming you have appropriately followed the above convention.

Also, you rarely should need to create your own coroutine scope like you are doing in your code. In this case, if your Fragment is recreated due to a configuration change or after the app is suspended and brought back, your coroutine could still be running, which leaks memory and has the potential to cause a crash if you try to access the Context. You should use viewLifecycleOwner.lifecycleScope to launch your coroutine in a Fragment. It is automatically stopped when the fragment view is destroyed.

  • Related