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

Kulwinder Singh

07/31/2019, 6:51 AM
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

gildor

07/31/2019, 6:53 AM
it depends on your code, there is no best way in general
k

Kulwinder Singh

07/31/2019, 6:55 AM
but what would be your preferred way if you have to do same thing using coroutines ? and why ?
t

thevery

07/31/2019, 10:42 AM
I'd use android handler for that, not coroutine
g

gildor

07/31/2019, 11:35 AM
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

Kulwinder Singh

07/31/2019, 1:57 PM
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

gildor

07/31/2019, 2:36 PM
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

Pablichjenkov

07/31/2019, 6:33 PM
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

thevery

07/31/2019, 6:34 PM
5 min looks like too short for workmanager
p

Pablichjenkov

07/31/2019, 6:38 PM
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

gildor

07/31/2019, 11:26 PM
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

vinay

08/01/2019, 2:22 AM
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

Jag

08/01/2019, 2:27 AM
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

Kulwinder Singh

08/02/2019, 9:16 AM
@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

gildor

08/02/2019, 9:18 AM
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

Kulwinder Singh

08/02/2019, 9:38 AM
Thanks, your method looks much better than mine, i will test it and let you know 👍
g

gildor

08/02/2019, 9:56 AM
Updated a bit, I misunderstood how MAX_ADS_PER_SESSION works, fixed
k

Kulwinder Singh

08/03/2019, 4:53 AM
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

gildor

08/03/2019, 5:46 AM
It's a bit different semantics from your original sample, so you don't accumulate ad free calls
k

Kulwinder Singh

08/03/2019, 5:51 AM
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
7 Views