Home > Back-end >  Kotlin cant change button properties
Kotlin cant change button properties

Time:10-05

I have a problem with changing the properties of a button in Kotlin. That is only in one function, it works fine in all others. It is weird because I can change the properties of a TextView in that function. Here is the function:

override fun onResponse(call: Call, response: Response) {
    val body = Klaxon().parse<API_result>(response.body!!.string())

    //setting these textView texts is no problem
    findViewById<TextView>(R.id.windSpeed).text = (body?.wind?.speed?.times(3.6)).toString()   " km/h"
    findViewById<TextView>(R.id.temperature).text = body?.main?.temp.toString()   " C"
    findViewById<TextView>(R.id.airPressure).text = body?.main?.pressure.toString()   " hPa"

    //changing button text is not working and crashes the app
    findViewById<Button>(R.id.fillOutForm).text = "Test"
}

Here is the whole file:

class MainActivity : AppCompatActivity() {

    lateinit var fusedLocationProviderClient: FusedLocationProviderClient
    private val client = OkHttpClient()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)

        fetchLocation()
    }

    private fun fetchLocation(){
        val task = fusedLocationProviderClient.lastLocation
        if(ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
            ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), 101)
            return
        }
        task.addOnSuccessListener {
            if(it != null){
                apiRequest(it.latitude, it.longitude)
            }
        }
    }

    private fun apiRequest(latitude: Double, longitude: Double){
        var url = "https://api.openweathermap.org/data/2.5/weather?lat="   latitude   "&lon="   longitude   "&appid=...&units=metric&lang=en";
        val request = Request.Builder()
            .url(url)
            .build()

        client.newCall(request).enqueue(object : Callback{
            override fun onFailure(call: Call, e: IOException) {
                return
            }

            override fun onResponse(call: Call, response: Response) {
                //code from above
            }
        })
    }
}

data class API_result(
    val main: main?,
    val wind: wind?
)

data class main(
    val temp: Double?,
    val pressure: Int?
)

data class wind(
    val speed: Double?
)

Before the app crashes, the button text gets updated correctly. If I remove the "button-line", the app works perfectly fine. I really don't know why that happens.

It seems like only just TextViews are working in this funciton, because I just also tested it with Password and Email (which are also Texts) and a Switch. It worked in non of these cases.

I also recognised that I can access the xml element (e.g. the button) in that function without any problem, but I just cant set the properties of it. For example, I can do this

findViewById<TextView>(R.id.someText).text = findViewById<Button>(R.id.someButton).text

but not this

findViewById<Button>(R.id.someButton).text = "exampleText"

Stacktrace: StackTrace

I hope you can help me! Thanks for your help in advance!

CodePudding user response:

I guess the issue here is that you are accessing the UI objects from a non UI thread and this is causing the issue. Accessing UI Element from a non UI Thread causes undefined behavior.

If you try to modify or even reference a UI object in a thread other than the main thread, the result can be exceptions, silent failures, crashes, and other undefined misbehavior.

You can read more about it here

To fix this problem you need to access the UI objects from a UI thread as below. Also its not good to do findViewById<TextView>(R.id.someText) inside the onResponse as you are sure that the UI objects are always there. Instead initialize them inside the onCreate and access the reference variable everywhere.

lifecycleScope.launch {
        withContext(Dispatchers.Main){
            textView.text = "sometext"
            button.text = "sometext"
            //and so on
        }
    }

if you don't have coroutines in your project

runOnUiThread(Runnable { 
        textView.text = "sometext"
            button.text = "sometext"
            //and so on
    })

There is also a similar question and answer here

  • Related