Hi, i want to schedule coroutine for every 5-9 min...
# coroutines
k
Hi, i want to schedule coroutine for every 5-9 minutes in my app, my actual requirement is to show ad after 5-9 minute so that's why i want to use coroutine to
delay
execution for random time between 5-9 mins, So what is best way to do it with kotlin in android ? Simply using
delay
or
flows/channels
?
g
it depends on your code, there is no best way in general
k
but what would be your preferred way if you have to do same thing using coroutines ? and why ?
t
I'd use android handler for that, not coroutine
g
Why Android handler? I mean for sure, adding coroutines just for this task is obvious overkill, but if you have coroutines it would be the same as handler (+ more flexible, if you do some other async code inside)
I mean if delay works for you, what else do you need? Flow would make sense if you just need stream of events and it make sense if you apply some operators and integrate with some reactive API, otherwise I don't see why do you need Flow/Channel
k
i aready have coroutine in my project, also delay is working but my requirement is to 1. delay for 5 mins 2. after 5 mins delay compled change some
boolean
like
isDelayCompleted = true
value to tell that delay is completed 3. then on any user event occurrence(ie: button click, swipe etc) then i want to check if that
isDelayCompleted
is
true
, so if it true then i will show ad and also again repeat from step 1-3 else if false do nothing So here if you note i don't have to start next delay immediately after first finish but i have to wait to consume or use value produced by first delay and then start another delay, so that's why i'm thinking if Flow/Channel can help me here. i have very good experience in coroutines but did not worked with channels so that's why i don't know if it will help in this case
g
Looks pretty simple, keep this state in some field, just encapsulate this behavior to a class, which has method that returns status (ShowAd or Wait) start coroutine with delay on first request to status, return state, update it to Wait, start coroutine which in 5 min set state to ShowAd
p
If it is an Android app it sounds more like a WorkManager thing than a regular Coroutines/Flow thing. After Android P if your app exits the foreground there is no guarantee any process scheduled will run. Your app executor threads/processes will be shutdown by the system.
t
5 min looks like too short for workmanager
p
The question mentioned 5-9 minutes. By the way, I think is a long time for a user to spend on an app unless it is a game. I am not UXUI expert anyways.
g
Yeah, WorkManager looks as huge overkill in this case. You know, I realized you really don’t need coroutines and background service, if your use case is what you described above, just memorize ad show time and check it on every access, if it more than 5 min after last show, show again, update timestamp, if less just do nothing
👍 2
v
AFAIK, WorkManager limits the minimum periodic job interval to 15 minutes. Even if you give a lower value, it gets clamped to 15 minutes.
💯 1
j
Agree, WorkManager or Alarm are probably not the right approach here given the short duration and the operation seems to be scoped to the lifecycle of the screen (rather than beyond the scope of the application). I like Andrey’s suggestion, its pretty straightforward. But if you already have a Flow for your user actions (eg. button click, swipe, etc) or for whatever reason you do need to go with a Flow approach then maybe you can do something like this:
Copy code
actionsFlow.zipWithDefault(adsFlow, AdsOperation.WaitForAd) { action, adOperation ->
    when (adOperation) {
        AdsOperation.ShowAd -> "$action: show ad"
        AdsOperation.WaitForAd -> "$action: wait for ad"
    }
}.collect { println(it) }
zipWithDefault
is a slight modification to the existing zip operation which just emits the default when nothing is available for emission from the second
flow
. The ads flow is very easy:
Copy code
fun adsFlow(duration: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) = flow {
    while (true) {
        delay(unit.toMillis(duration))
        emit(AdsOperation.ShowAd)
    }
}
But I’d go with Andrey’s approach if it works for you 🙂
k
@gildor method looks perfect, i have also created this implementation using coroutine. let me know if anything wrong in this, if so then i will use @gildor method
Copy code
class AdFreeMethod1(lifecycleScope: LifecycleCoroutineScope) : AdFreeCalls {

    companion object {
        private const val MAX_ADS_PER_SESSION = 10
    }

    private var adsToShow = 0

    init {
        lifecycleScope.launch {
            for (i in 0..MAX_ADS_PER_SESSION) {
                val delayTime = (4.minute..7.minute).random()
                delay(delayTime)
                adsToShow += 1
            }
        }
    }

    override fun isAdFreeCall(): Boolean {
        val isAdFreeCall = adsToShow == 0
        if (adsToShow != 0) {
            adsToShow -= 1
        }
        return isAdFreeCall
    }
    private val Int.minute: Long
        get() = this * 60000L
}
g
You don’t need coroutine for this, just update
adsToShow
on
isAdFreeCall()
by comparing current system time with last system time
Not tested, just fast implementation:
Copy code
class AdFreeMethod(
        private val minDelaySec: Long,
        private val maxDelaySec: Long,
        private val maxAdsPerSession: Long = 10,
        initialAdsToShow: Long = 0L
) : AdFreeCalls {

    private var showedAds = 0
    private var adsToShow = initialAdsToShow
    private var lastUpdateTime = System.currentTimeMillis()

    override fun isAdFreeCall(): Boolean {
        if (showedAds >= maxAdsPerSession) return true

        val oldUpdateTime = lastUpdateTime
        lastUpdateTime = System.currentTimeMillis()

        val timeFromLastRequest = lastUpdateTime - oldUpdateTime
        // Semantics a bit different, only 1 random,
        // you can just change this code to get random intervals for each period
        adsToShow += timeFromLastRequest / Random.nextLong(minDelaySec, maxDelaySec)
        // -1 from amount of free calls for this call
        adsToShow--
        adsToShow = adsToShow.coerceAtLeast(0)
        val adFreeCall = adsToShow == 0L
        if (!adFreeCall) {
            showedAds++
        }
        return adFreeCall
    }
}
👍 1
k
Thanks, your method looks much better than mine, i will test it and let you know 👍
g
Updated a bit, I misunderstood how MAX_ADS_PER_SESSION works, fixed
k
Thanks @gildor, your method really helped me a lot. i have done some changes in code according to my requirements and removed some unnecessary variables like
adsToShow
because i had created those only for coroutines. so here is final version i'm using
Copy code
class AdFreeMethod(
        private val minDelayMillis: Long = 4.minuteToMillis,
        private val maxDelayMillis: Long = 7.minuteToMillis,
        private val maxAdsPerSession: Long = 10
) : AdFreeCalls {

    private var showedAds = 0
    private var lastAdTime = System.currentTimeMillis()
    private var nextAdTime = lastAdTime + getNextDelay()

    override fun isAdFreeCall(): Boolean {
        if (showedAds >= maxAdsPerSession) return true

        val currentTime = System.currentTimeMillis()

        return if (currentTime >= nextAdTime) {
            showedAds++
            nextAdTime = currentTime + getNextDelay()
            lastAdTime = currentTime
            false
        } else true
    }

    private fun getNextDelay() = Random.nextLong(minDelayMillis, maxDelayMillis)

}
g
It's a bit different semantics from your original sample, so you don't accumulate ad free calls
k
i was using
adFreeCalls
only for coroutines, because in coroutine after one delay instantly i have to start next delay and therefore i want to keep record of number of delays. but now with current logic i don't need that