Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin: runOnUiThread Unresolved Reference

Tags:

I am attempting to use Activity.runOnUiThread(Runnable), but Android Studio seems to think that the function does not exist. I've made sure "android.app.Activity" is imported.

I am trying to use threads to connect to a bluetooth device and read the data. I've listed the paired devices and clicking one of the paired devices brings me to a second screen where I can then connect/disconnect the socket from the selected device. Connecting and disconnecting is working.

I'm now trying to send the data stream back to the UI thread from the readThread inner class, but I can't get runOnUiThread to work. No matter what I do runOnUiThread is not recognized.

package com.example.idxdatalogger

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import android.view.View
import android.app.Activity
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_second.*
import java.io.IOException
import java.io.InputStream
import java.util.*



class SecondActivity : AppCompatActivity() {
    var btAdapter: BluetoothAdapter? = null

    var data = arrayListOf<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        btAdapter = BluetoothAdapter.getDefaultAdapter()

        val position = (intent.getStringExtra("position"))!!.toInt()
        val uuid = UUID.fromString(intent.getStringExtra("UUID"))

        //textView.text = position.toString()
        //textView2.text = uuid.toString()

        val pairedDevices: Set<BluetoothDevice> = btAdapter!!.bondedDevices

        btDeviceLabel.text = pairedDevices.elementAt(position).name.toString()

        var inStream = connectStream(uuid, position, pairedDevices.elementAt(position), btAdapter)

        conStat(inStream)

        connectButton.setOnClickListener {
            inStream.start()
            conStat(inStream)
        }

        cancelButton.setOnClickListener {
            inStream.cancel()
            conStat(inStream)
            inStream = connectStream(uuid, position, pairedDevices.elementAt(position), btAdapter)
        }

    }

    /*
    change text and button visiblity for connect/disconnect buttons
     */
    fun conStat(inStream: connectStream) {
        if (inStream.socket!!.isConnected) {
            socketStatus.text = getString(R.string.connectionOpened)
            connectButton.visibility = View.INVISIBLE
            cancelButton.visibility = View.VISIBLE
        } else {
            socketStatus.text = getString(R.string.connectionClosed)
            connectButton.visibility = View.VISIBLE
            cancelButton.visibility = View.INVISIBLE
        }

    }

    class connectStream(
        private val uuid: UUID,
        private val position: Int,
        private val device: BluetoothDevice,
        private val adapter: BluetoothAdapter?
    ) : Thread() {
        val socket: BluetoothSocket? = device.createRfcommSocketToServiceRecord(uuid)

        override fun run() {

            adapter?.cancelDiscovery()

            try {
                socket?.connect()

                manageConnection().readThread(socket!!).start()
            } catch (e: IOException) {
                Log.i("SOCKET: ", "Failed to connect")
                Log.i("UUID: ", uuid.toString())
                Log.i("Device: ", device.name)
                Log.i("Address: ", device.address)
            }
        }

        fun cancel() {
            try {
                socket?.close()
            } catch (e: IOException) {
                Log.i("SOCKET: ", "Could not close socket")
            }
        }

    }

    class manageConnection() {
        private val MESSAGE_READ: Int = 0
        inner class readThread(socket: BluetoothSocket) : Thread() {
            private val inStream: InputStream = socket.inputStream
            private val inBuffer: ByteArray = ByteArray(1024)

            override fun run() {
                var readBytes: Int

                while (true) {
                    readBytes = try { inStream.read(inBuffer)
                    } catch (e: IOException) {
                        Log.d("IN_STREAM: ", "Input stream disconnected")
                        break
                    }
                    SecondActivity.runOnUiThread(Runnable {

                    })
                }
            }
        }
    }
}

Edit:

Re-written my program to use Thread(Runnable{}) instead of having separate class instances for each thread. runOnUiThread is now working without issue. I've posted my updated code below, but this is not yet fully functional. For now, we can consider the issue solved.

package com.example.idxdatalogger

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.util.Log
import android.view.View
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.*
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_second.*
import java.io.IOException
import java.io.InputStream
import java.util.*

/*

 */
class SecondActivity : AppCompatActivity() {
    var btAdapter: BluetoothAdapter? = null
    var data = arrayListOf<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        btAdapter = BluetoothAdapter.getDefaultAdapter()

        val position = (intent.getStringExtra("position"))!!.toInt()
        val uuid = UUID.fromString(intent.getStringExtra("UUID"))

        val pairedDevices: Set<BluetoothDevice> = btAdapter!!.bondedDevices
        val device = pairedDevices.elementAt(position)

        btDeviceLabel.text = pairedDevices.elementAt(position).name.toString()

        val socket: BluetoothSocket? = device.createRfcommSocketToServiceRecord(uuid)

        //var inStream = connectStream(uuid, position, pairedDevices.elementAt(position), btAdapter)
        conStat(socket!!)


        val inStream: InputStream = socket.inputStream
        val inBuffer: ByteArray = ByteArray(1024)

        var readBytes : Int

        connectButton.setOnClickListener {
            //connect to device
            Thread(Runnable {
                kotlin.run {
                    btAdapter?.cancelDiscovery()
                    try {
                        socket.connect()
                        runOnUiThread(Runnable { conStat(socket) })
                        //read stream
                        Thread(Runnable{
                            while(true) {
                                readBytes = try {
                                    inStream.read(inBuffer)
                                } catch (e: IOException) {
                                    Log.d("IN_STREAM: ", "input stream disconnected")
                                    break
                                }
                                //return stream information to UI thread
                                runOnUiThread(Runnable {
                                    data.add(inBuffer.toString())
                                    dataList.adapter = dataListAdapter(this,data)
                                    dataList.visibility = View.VISIBLE
                                })
                            }
                        }).start()
                    } catch (e: IOException) {
                        Log.i("SOCKET: ", "Failed to connect")
                        Log.i("UUID: ", uuid.toString())
                        Log.i("Device: ", pairedDevices.elementAt(position).name)
                        Log.i("Address: ", pairedDevices.elementAt(position).address)
                    }
                }
            }).start()

        }

        cancelButton.setOnClickListener {
            //close socket connection
            Thread(Runnable {
                try {
                    socket.close()
                } catch (e: IOException) {
                    Log.i("SOCKET: ", "Could not close socket")
                }
                runOnUiThread(Runnable {
                    conStat(socket)
                    val intent = Intent(this, MainActivity::class.java)
                    startActivity(intent)
                })
            }).start()
        }
    }

    /*
    change text and button visiblity for connect/disconnect buttons
     */
    fun conStat(socket : BluetoothSocket) {
        if (socket.isConnected) {
            socketStatus.text = getString(R.string.connectionOpened)
            connectButton.visibility = View.INVISIBLE
            cancelButton.visibility = View.VISIBLE
        } else {
            socketStatus.text = getString(R.string.connectionClosed)
            connectButton.visibility = View.VISIBLE
            cancelButton.visibility = View.INVISIBLE
        }
    }
like image 926
KjerfulCoder Avatar asked Aug 02 '19 16:08

KjerfulCoder


2 Answers

SecondActivity.runOnUiThread(...) is incorrect, since you are calling method on a class and runOnUiThread() method is not static. You need to call this method on an instance of Activity class, not on the class itself.

like image 177
Sergei Emelianov Avatar answered Sep 20 '22 15:09

Sergei Emelianov


Please take a look into the runOnUiThread signature here:

    /**
 * Runs the specified action on the UI thread. If the current thread is the UI
 * thread, then the action is executed immediately. If the current thread is
 * not the UI thread, the action is posted to the event queue of the UI thread.
 *
 * @param action the action to run on the UI thread
 */
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

it's obvious that it's not a static method, so you could not call this method as a static method like this:

SomeActivity.runOnUiThread {}

A work-around could be to pass the SecondActivity as an instance, and then easily do whatever you want with your activity instance.

class myClass(val activity : Activity){

    fun someMethod(){
         activity.runOnUithread{}
        }
}
like image 36
CodeRanger Avatar answered Sep 23 '22 15:09

CodeRanger