How to connect to socket and send and receive data from raw WebSocket — Android.

Bharat Kumar
4 min readFeb 7, 2023

What are web sockets?

According to Open Ai Chat GPT - WebSocket is a protocol that enables full-duplex communication between a client and a server bla bla bla. As this article will be read by an Android developer the only thing you need to know is web sockets are a real time live communication between server and client. For example the firebase Realtime database or chat servers etc. Obviously firebase has its own SDK to do it but what id someone gave you a URL like wss://ws.funSocket.com

Is there any library we will use to connect to WebSocket?

Yes, we will use Scarlet Library developed by “TINDER” yes the dating one.

Let’s Begin

Add the latest scarlet dependency to your build.gradle(app)

    def scarletVersion = '0.1.12'
implementation "com.tinder.scarlet:scarlet:$scarletVersion"
implementation "com.tinder.scarlet:websocket-okhttp:$scarletVersion"
implementation "com.tinder.scarlet:lifecycle-android:$scarletVersion"
implementation "com.tinder.scarlet:message-adapter-gson:$scarletVersion"
implementation "com.tinder.scarlet:stream-adapter-coroutines:$scarletVersion"

Here the first one is for base scarlet, second one is for Okhttp, 3rd is for lifecycle support, 4th is for Gson and the last is for coroutine support.

Scarlet also has Moshi, Jackson, Rx-Java etc support, but we will use gson and coroutines Flows.

We need to create and provide the instance of Scarlet. It is mostly similar to Retrofit. Here I am using HILT as my dependency injection tool so this is the code of my AppModule file.

   @Singleton
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
}

@Singleton
@Provides
fun provideSocketApi(
app: Application,
okHttpClient: OkHttpClient,
gson: Gson
): WebSocketApi {
return Scarlet.Builder()
.backoffStrategy(LinearBackoffStrategy(1000))
.lifecycle(AndroidLifecycle.ofApplicationForeground(app))
.webSocketFactory(
okHttpClient.newWebSocketFactory(
"wss://ws.bitstamp.net"
)
)
.addStreamAdapterFactory(FlowStreamAdapter.Factory)
.addMessageAdapterFactory(GsonMessageAdapter.Factory(gson))
.build()
.create()
}

@Singleton
@Provides
fun provideGsonInstance(): Gson {
return Gson()
}

Here to create scarlet builder you need the instance of Gson and OkHttpClient which you can see i have provided it to the provideSocketApi funtion. To create the Scarlet builder same as we do in Retrofit Builder we have some configurations here -

  1. backoffStratergy — As i understood this stratergy will be used to reconnect when the connection fails. Here we used LinearBackOffStartergy(1000) which means it will try to reconnect after every 1000ms.
  2. lifecycle — The lifecycle to which socket is connected to.
  3. streamAdaperFactory — Here we are using FlowStreamAdapter.Factory which is custom util used to convert the incoming stream to Flow to use it with convinence.
  4. messageAdapterFactory — Here we are using GsonMessageAdapter same as we do in retrofit converter factory this is already provided by scarlet and also supports Moshi and Jackson.

Creating an Interface -

interface WebSocketApi {

@Receive
fun observeEvent(): Flow<WebSocket.Event>

@Send
fun sendModel(socketModel: SocketModel):Boolean

@Receive
fun observeSocketData():Flow<SocketResponse>
}

Here we have an Interface which we have already provided with Hilt.

There are 2 basic annotation methods with which you can interact with web socket.

Receive — Annotate Receive with the function you will use to receive the data from socket

Send- Annotate Send with the function you want to send data to socket.

The function observeEvents of socket like connection opened, closed, error, message received etc.

The sendModel() will send the SocketModel data class to the socket you can change it to whatever data class you want and the observeSocketData() is returning a flow of the response so that we can collect and watch the data.

@HiltViewModel
class WebSocketViewModel @Inject constructor(
private val webSocketApi: WebSocketApi
):ViewModel() {
private val connectionEventChannel = Channel<WebSocket.Event>()
val connectionEvent = connectionEventChannel.receiveAsFlow().flowOn(Dispatchers.IO)

private val socketEventChannel = Channel<SocketResponse>()
val socketEvent = socketEventChannel.receiveAsFlow().flowOn(Dispatchers.IO)

fun observeEvents() {
viewModelScope.launch(Dispatchers.IO) {
webSocketApi.observeEvent().collect { event ->
connectionEventChannel.send(event)
}
}
}

fun observeBaseModels() {
viewModelScope.launch(Dispatchers.IO) {
webSocketApi.observeReceive().collect { data ->
socketEventChannel.send(data)
}
}
}

fun sendBaseModel(data: SocketModel) {
viewModelScope.launch(Dispatchers.IO) {
webSocketApi.sendModel(data)
}
}

init {
observeEvents()
observeBaseModels()
}
}

In my view model I have the functions to collect and emit data.

And here is how you collect in in your activity or fragment.

        lifecycleScope.launchWhenStarted {
viewModel.connectionEvent.collectLatest{
when(it)
{
is WebSocket.Event.OnConnectionOpened<*> -> {
Log.d("TAG", "onCreate: OnConnectionOpened")
viewModel.sendBaseModel(SocketModel(Data("live_trades_btcusd"),"bts:subscribe"))
}
is WebSocket.Event.OnMessageReceived -> {
Log.d(TAG, "initView: "+it.toString())
}
is WebSocket.Event.OnConnectionClosing -> {
Log.d("TAG", "onCreate: OnConnectionClosing ${it.shutdownReason}")
}
is WebSocket.Event.OnConnectionClosed -> {
Log.d("TAG", "onCreate: OnConnectionClosed ${it.shutdownReason}")
}
is WebSocket.Event.OnConnectionFailed -> {
Log.d("TAG", "onCreate: OnConnectionFailed ${it.throwable}")

}
}
}
}

lifecycleScope.launchWhenStarted {
viewModel.socketEvent.collectLatest{
Log.d("TAG", "onCreate: $it")
}
}

Collecting socketEvent has nothing much to talk about you will get the data here after connection.

On Collecting WebSocket.Event we get many events such as -

  1. OnConnectionOpened for getting when Connection is opened or socket makes a connection.
  2. OnConnectionFailed will provide you with a throwable as why it failed.
  3. OnConnectionClosed and OnConnectionClosing will give you the reason when socket shutsdown.
  4. OnMessageReceived will give you the message you received in a text format which we don't need as we will be already getting the response from socketEvent but you can use it and convert the text into data class and work with it also.

Here there is a small point to note. If you want to send some data to receive some data from the socket as soon as your activity or fragment comes don’t do it in the onCreate as you will be sending the data before socket makes a connection to the server so you need to do it in the OnConnectionOpened Event as i called viewModel.sendBaseModel(SocketModel(Data(“live_trades_btcusd”),”bts:subscribe”)) there.

I hope this article is helpful. If you think something is missing, have questions, or would like to give feedback, go ahead and leave a comment below. I’d appreciate the feedback.

This is my first Medium article!! Please let me know if it was helpful in anyway.

Enjoy you learning and keep sharing :}

--

--