1

I've the below code that was working fine with RecyclerView, in the same time, I've a websocket that works for onMessage and print the received msg correctly.

Then I tried to merge them together, i.e. receive data from socket and push it directly to the RecyclerView but it is not showing anything in RecyclerView screen

During one of my trials, I got this error:

I/Websocket: Error Only the original thread that created a view hierarchy can touch its views.

lateinit var mWebSocketClient: WebSocketClient
lateinit var uniqueID: String
lateinit var rv: RecyclerView

val chaptersList: ArrayList<String> = ArrayList()

class MainActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        rv = rvChapterList    //  "@+id/rvChapterList" of RecyclerView

         // chaptersList.add(s)
         //   rvChapterList.layoutManager = layoutManager
         //   rvChapterList.adapter = ChapterAdapter(this, chaptersList)
        var context= this
        connectWebSocket(context)
}


fun connectWebSocket(context: MainActivity) {
    val uri: URI
    try {
        uri = URI("ws://10.0.2.2:8080/ws")
    } catch (e: URISyntaxException) {
        e.printStackTrace()
        println("ws faild")
        return
    }

    mWebSocketClient = object : WebSocketClient(uri) {
        override fun onOpen(serverHandshake: ServerHandshake) {
            Log.i("Websocket", "Opened")
            mWebSocketClient.send("Hello from " + Build.MANUFACTURER + " " + Build.MODEL)
        }

        override fun onMessage(s: String) {
            Log.i("Websocket", "Recieved $s")  // This is working
            chaptersList.add(s)
            Log.i("chaptersList", "chaptersList $s")  // This is working

            rv.layoutManager = LinearLayoutManager(context)
            rv.adapter = ChapterAdapter(context , chaptersList)  // This is not working

        }
    }
    mWebSocketClient.connect()
}

UPDATE

Referring to this answer, I tried the below, but it did not work:

lateinit var rv: RecyclerView
lateinit var layoutManager: RecyclerView.LayoutManager
lateinit var adaptor: ChapterAdapter

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
            layoutManager = LinearLayoutManager(this)
            rv = rvChapterList
            adaptor = ChapterAdapter(this, chaptersList)
           rv.adapter = adaptor
           rv.layoutManager = layoutManager
           rv.adapter = ChapterAdapter(this, chaptersList)
    }
}

fun connectWebSocket(context: MainActivity) {
   override fun onMessage(s: String) {
            val insertIndex = chaptersList.size
            chaptersList.add(insertIndex, s)
            adaptor.notifyItemInserted(insertIndex)

            adaptor.notifyDataSetChanged()
   }
}
Hasan A Yousef
  • 15,770
  • 15
  • 88
  • 140

1 Answers1

3

Views can be reached only within main thread. As onMessage() method runs on worker thread (background), it cannot make changes on view directly. There are several ways to make changes on views from worker thread like androd.os.Handler. In your case runOnUiThread(Runnable) method, which is provided by Activity.class, is a good choice. It takes runnable as an argument and runs it on UI thread. In your case it can be used as following:

override fun onMessage(s: String) {
    val insertIndex = chaptersList.size
    chaptersList.add(insertIndex, s)

    Thread {
        runOnUiThread {
            adaptor.notifyItemInserted(insertIndex)  
        }
    }.run()
}

Update: Another more reliable way is using LiveData from Android Architecture Components. As LiveData is an observable and lifecycle-aware data holder class, it can be used like this:

Another class file:

class SocketClient {
    val message = MutableLiveData<String>()

    fun connectWebSocket(context: Context) {
        // Setup stuff

        mWebSocketClient = object : WebSocketClient(uri) {
            override fun onOpen(serverHandshake: ServerHandshake) {
                // Opened do some stuff
            }

            override fun onMessage(s: String) {
                message.postValue(s)
            }
        }
        mWebSocketClient.connect()
    }
}

And within activity's onCreate():

val client = SocketClient()

client.message.observe(this, Observer { s: String ->
    val insertIndex = chaptersList.size
    chaptersList.add(insertIndex, s)
    adaptor.notifyItemInserted(insertIndex)
})

client.connectWebSocket(this)

Before using LiveData, it is required to add dependencies. See official documentation. as:

    def lifecycle_version = "2.0.0"
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
Hasan A Yousef
  • 15,770
  • 15
  • 88
  • 140
Nurbol
  • 371
  • 2
  • 8
  • Thanks, this is if `connectWebSocket()` is running within the `MainActivity` scope, but if it is running outside it, I get that I need to define the `runOnUiThread` function, what can I do in this case? – Hasan A Yousef Jul 12 '19 at 22:33
  • 1
    Updated my post @HasanAYousef – Nurbol Jul 12 '19 at 23:02