Implementing long polling with retrofit and kotlin channels
Asked Answered
K

1

8

I am trying to implement long polling which is lifecycle aware(in Activity/Fragment). The polling will be scoped to the fragment which sends API request to the server every fixed interval of time. However, I am unable to implement it.

This is how I want the implementation to be like

  • Have a hard timeout on client-side without considering any extra delay incurred in receiving the response.
  • Wait for the response of previous API call before sending the next request. i.e., the request in polling queue should wait for response irrespective of its priority due to polling interval

Consider:

HARD_TIMEOUT = 10s

POLLING_INTERVAL = 2s enter image description here

 Request 1: Started at: 0sec      Response delay:1.2sec   Duration left: 0.8sec
 Request 2: Started at: 2sec      Response delay:0.4sec   Duration left: 1.6sec
 Request 3: Started at: 4sec      Response delay:2.5sec   Duration left: 0sec
 Request 4: Started at: 6.5sec    Response delay:0.5sec   Duration left: 1.0sec
 Request 5: Started at: 8sec      Response delay:0.8sec   Duration left: 1.2sec

For this use case, I want to use polling instead of socket. Any Idea/solutions would be appreciated. Thank you.

Kimbell answered 6/4, 2020 at 9:20 Comment(2)
you can use ticker, for example pl.kotl.in/mP5Rl4k0KIonopause
@Ionopause ticker is annotated with ObsoleteCoroutinesApi, can't use it in production, a redesign of could possibly crash or make the app behave unexpectedly.Kimbell
K
4

Ok, Figured out a solution for polling using channels. this should help someone searching for an example.

    private val pollingChannel = Channel<Deferred<Result<OrderStatus>>>()
    val POLLING_TIMEOUT_DURATION = 10000L
    val POLLING_FREQUENCY = 2000L

A channel is required to hold your asynchronous request just in case more request comes in while your async task is being executed.

    val pollingChannel = Channel<Deferred<Pair<Int,Int>>>()

QUEUE EXECUTOR: It will pick an async task and start executing them in FIFO order.

    CoroutineScope(Dispatchers.IO).launch {
        for (i in pollingChannel) {
            val x = i.await()
            println("${SimpleDateFormat("mm:ss.SSS").format(Calendar.getInstance().time)} Request ${x.first}: value ${x.second}")
        }
    }

POLLING FUNCTION: Adds your async task to the polling channel every fixed interval of time until timeout.

    CoroutineScope(Dispatchers.IO).launch {
        var reqIndex = 1
        val timedOut = withTimeoutOrNull(POLLING_TIMEOUT_DURATION) {
            while (receiverJob.isActive) {
                pollingChannel.send(async {
                    getRandomNumber(reqIndex++)
                })
                delay(POLLING_FREQUENCY)
            }
        }
    }

ASYNCHRONOUS OPERATION

to avoid answer being verbose I created a function with a random delay, please replace with the required API call

   private suspend fun getRandomNumber(index: Int): Pair<Int,Int> {
    val randomDuration = (1..6L).random() * 500
    delay(randomDuration)
    return Pair(index,(0..100).random())
}

SAMPLE OUTPUT

enter image description here

Kimbell answered 8/4, 2020 at 19:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.