0

I implemented WebSocket with WebSocketListener. And I want to use coroutine channel. But now I need to call it in Coroutine Scope. Here, I don't have viewModelScope nor lifecycleScope. So, I used GlobalScope. And I think this might cause memory leak and incorrect behaviour.

@OptIn(DelicateCoroutinesApi::class)
class CustomWebSocketListener : WebSocketListener() {
    val socketEventChannel: Channel<SocketUpdate> = Channel(10)

    override fun onOpen(webSocket: WebSocket, response: Response) {
        // no-op
    }

    override fun onMessage(webSocket: WebSocket, text: String) {
        GlobalScope.launch {
            socketEventChannel.sendOrNothing(SocketUpdate(text = text))
        }
    }

    override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
        GlobalScope.launch {
            socketEventChannel.sendOrNothing(SocketUpdate(exception = SocketAbortedException()))
        }
        webSocket.close(1000, null)
        socketEventChannel.close()
    }

    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
        GlobalScope.launch {
            socketEventChannel.sendOrNothing(SocketUpdate(exception = t))
        }
    }

    private suspend fun <E> Channel<E>.sendOrNothing(e: E) {
        try {
            this.send(e)
        } catch (e: ClosedSendChannelException) {
            e.printStackTrace()
        }
    }
}

I use CustomWebSocketListener like this:

    suspend fun startOrReloadSocket(url: String): Channel<SocketUpdate> {
        if(isConnected()) {
            if(url == webSocketUrl) {
                return webSocketListener!!.socketEventChannel
            } else {
                stopSocket()
            }
        }
        return with(CustomWebSocketListener()) {
            startOrReloadSocket(url,this)
            this@with.socketEventChannel
        }
    }

I made this for using the listener. How can I include the listener in the same coroutine scope so that I can use viewModelScope?(the suspend function(startOrReloadSocket) is called in the viewModelScope)

Ethan Choi
  • 2,339
  • 18
  • 28
c-an
  • 3,543
  • 5
  • 35
  • 82
  • Why not use `trySend`? – Ethan Choi Jul 26 '22 at 04:59
  • does `startOrReloadSocket` exist in your VM? if so, you can pass a scope through that function to the constructor of `CustomWebSocketListener` or use something like `trySend` as Ethan suggested – William Reed Jul 26 '22 at 14:52
  • @EthanChoi I don't know it is used.. can you explain more? – c-an Jul 26 '22 at 16:29
  • @WilliamReed sending scope to listener class looks weird because I feel like listener doesn't receive anything but only listens something. – c-an Jul 26 '22 at 16:31

1 Answers1

0

How can I use suspend function or viewModelScope/lifecycleScope in WebSocketListener?

produce with trySend, consume on Lifecycle

eventChannel isn’t rendezvous channel. thus, you just event produce with trySend and consume on lifecycle.

Produce

/**
* If you want handle result of event produce :
*
* ChannelResult result = socketEventChannel.trySend(~)
* if(result.isFailure) {
*    handle error
* }
**/

override fun onMessage(webSocket: WebSocket, text: String) {
    socketEventChannel.trySend(SocketUpdate(text = text))
}

override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
    socketEventChannel.trySend(SocketUpdate(exception = SocketAbortedException()))
    webSocket.close(1000, null)
    socketEventChannel.close()
}

override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
    socketEventChannel.trySend(SocketUpdate(exception = t))
}

Consume

viewModelScope or lifecycleScope.launch {
    listener.socketEventChannel.consumeEach {
        // TODO : handle socket event
    }
}

Ref

trySend

Ethan Choi
  • 2,339
  • 18
  • 28