https://kotlinlang.org logo
#coroutines
Title
# coroutines
s

Szymon Jeziorski

02/07/2023, 1:11 PM
Hello, I have a use case where after some trigger I'm fetching some data from external API and acting upon in continuously with delay mentioned in that data. Let it be for example WeatherDataToken with
expiresAt
,
refreshAllowedAfter
and business payload within it. It doesn't make sense to try refreshing it before
refreshAllowedAfter
and I would like to initiate some action when token expires and is not correctly refreshed in time, for example notify some external service about this fact. The simplest and naive solution would be to use Java Timers, but I would like to leverage coroutines as creating new thread per each operation and managing it sound like an overkill for me. Below is something I prototyped, but am not sure about the pitfalls of this approach as I haven't had similar use cases yet. What do you think about it? Would it be fine to use something like this or do you see some red flags straight away with a better alternative in mind? Thanks in advance.
Copy code
private val timerScope = Executors.newSingleThreadExecutor()
    .asCoroutineDispatcher()
    .let { dispatcher -> CoroutineScope(dispatcher + SupervisorJob()) }

class Weather(private val weatherApiClient: WheaterApiClient) {
    var weatherApiToken: WeatherApiToken? = null
        private set

    private var tokenRefreshJob: Job? by Delegates.observable(null) { _, oldJob, _ -> oldJob?.cancel() }
    private var weatherDataExpiredNotifyJob: Job? by Delegates.observable(null) { _, oldJob, _ -> oldJob?.cancel() }

    fun initiateWeatherToken() {
        ...
        scheduleWeatherTokenRefresh()

    }

    private fun scheduleWeatherTokenRefresh() {
        tokenRefreshJob = timerScope.launch {
            while (isActive) {
                runCatching {
                    val newToken = weatherApiClient.getWeatherDataToken()
                    weatherApiToken = newToken
                    weatherDataExpiredNotifyJob = timerScope.launch { notifyAboutWeatherTokenExpiry(newToken) }
                    delay(newToken.refreshAllowedAfter.millisFromNow())
                }.onFailure { [onFailureCallback] }
            }
        }
    }

    private suspend fun notifyAboutWeatherTokenExpiry(token: WeatherApiToken) {
        delay(token.expiresAt.millisFromNow())
        [some logic]
    }
}
e

ephemient

02/07/2023, 1:34 PM
you could use Flow primitives to build something like
Copy code
suspend fun pollWeatherToken() {
    flow {
        while (true) {
            val newToken = ...
            emit(newToken)
            delay(newToken.refreshAllowedAfter.millisFromNow())
        }
    }
        .collectLatest { token ->
            delay(token.expiresAt.millisFromNow())
            [some logic]
        }
}
where the
collectLatest
coroutine gets automatically cancelled every time there is a new upstream emission