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

Time:02-15

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.

CodePudding user response:

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