How to create shared webSocket for two fragments and one activity?

Viewed 180

Right now my app has one single activity which has full support of WS actions with listeners and creating new instance of webSocket. I would like to decompose this to one activity which will be like a holder for several fragments.

At the beginning I would like to create webSocket in the activity and then work with created webSocket on fragments. I work with okHttp3 websockets. In general I can for example create one single viewModel where I will initialize websocket, but I need to pass also listener during WS creating:

val loggingInterceptor: HttpLoggingInterceptor = HttpLoggingInterceptor()
            .setLevel(HttpLoggingInterceptor.Level.BODY)


val client = OkHttpClient.Builder()
            .addInterceptor(AuthToken(this))
            .addInterceptor(loggingInterceptor)
            .build()

val request = Request.Builder()
            .url(
                BuildConfig.CHAT_URL + UtilsClass.userInfo.applicant?.id + "/"
            )
            .addHeader("Sec-WebSocket-Protocol","android")
            .build()


val ws = client.newWebSocket(request, listener) <---- here

on the other hand I need to have separate listeners for separate fragments. The call of .newWebSocket(... will create a new instance of webSocket what is not good for me. But when I will have one single shared webSocket listener I will need to handle from which fragment I wait info. Also maybe I decided to do a wrong thing which won't be good.

1 Answers

To use this solution you must be familiar with these fundamentals

  1. MVVM architecture
  2. Kotlin flows (reactive programming)

first, create a sealed data class to handle socket events:

sealed class SocketEvent {
    object OpenEvent : SocketEvent()
    data class CloseEvent(val code: Int, val reason: String) : SocketEvent()
    data class Error(val error: Throwable) : SocketEvent()
    data class StringMessage(val content: String) : SocketEvent()
}

second, you must create a view model class that holds the web socket object something like this:

class SocketViewModel : ViewModel() {


    private lateinit var client: OkHttpClient
    private lateinit var socket: WebSocket

    private fun attachWebSocketListener(webListener: WebSocketListener) {
        client = OkHttpClient()
        val request = Request.Builder().url("http://195.201.167.116:9502").build()
        socket = client.newWebSocket(request, webListener)
    }

    suspend fun socketEventsFlow(): Flow<SocketEvent?> = callbackFlow {
        Timber.tag("soxum").d("opening socket")
        val socketListener = object : WebSocketListener() {
            override fun onMessage(webSocket: WebSocket, text: String) {
                trySendBlocking(SocketEvent.StringMessage(text))
            }

            override fun onOpen(webSocket: WebSocket, response: Response) {
                trySendBlocking(SocketEvent.OpenEvent)
            }

            override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
                trySendBlocking(SocketEvent.CloseEvent(code, reason))
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                trySendBlocking(SocketEvent.Error(t))
                cancel("", t)
            }
        }
        attachWebSocketListener(socketListener)
        awaitClose { socket.cancel() }
    }.flowOn(Dispatchers.IO).shareIn(viewModelScope, SharingStarted.Lazily)
}

in your fragments initial view model like this to your view model depends on the parent activity :

 class FirstFragment : Fragment() {

    private val viewModel by activityViewModels<SocketViewModel>()


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.socketEventsFlow.collectLatest {
                    // TODO get event and do what you want to to
                }
            }
        }
        return inflater.inflate(
            R.layout.fragment_first,
            container,
            false
        )

    }
    
}

activity code :

class MainActivity : AppCompatActivity() {

    private val viewModel by viewModels<SocketViewModel>()

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.socketEventsFlow.collectLatest {
                    // TODO get event and do what you want to to
                }
            }
        }
     }
}
Related