Implementing Reward Popups (Gamification) in Mobile Apps
A reward popup is the climactic moment in user interaction flow. The user completes a quest, makes a purchase, reaches a milestone — and the app must respond meaningfully. A boring AlertDialog kills the moment. A properly implemented popup with animations, particles, and haptic feedback transforms a technical event into "wow."
Reward Popup Anatomy
Typical layer-based structure:
-
Backdrop: dimmed background with blur effect (
UIBlurEffect/RenderEffect.createBlurEffect) - Card: entrance from bottom or via scale with spring animation
- Reward icon: scale + rotation with bounce
-
Particle burst: confetti, stars, coins —
CAEmitterLayeror Lottie - Text reveal: score counter, animating from previous to new value
- CTA button: appears with delay after main animation
The entire sequence takes 1.2–2 seconds. Longer and users start tapping close.
Animated Number Counter
The most detail-oriented element is the animated counter. +500 coins should count up, not simply appear.
class CountingLabel: UILabel {
func countFrom(_ start: Int, to end: Int, duration: TimeInterval) {
let displayLink = CADisplayLink(target: self, selector: #selector(update))
self.start = start; self.end = end
self.startTime = CACurrentMediaTime()
self.duration = duration
displayLink.add(to: .main, forMode: .common)
}
@objc func update(link: CADisplayLink) {
let elapsed = CACurrentMediaTime() - startTime
let progress = min(elapsed / duration, 1.0)
let eased = 1 - pow(1 - progress, 3) // ease-out cubic
currentValue = Int(Double(start) + Double(end - start) * eased)
text = "+\(currentValue)"
if progress >= 1 { link.invalidate() }
}
}
In Flutter — TweenAnimationBuilder<int> with curve: Curves.easeOut and Text('${value}') inside.
Particle Burst
For a one-time confetti burst on popup appearance — CAEmitterLayer with birthRate = 300 for the first 0.1 seconds, then birthRate = 0. This creates an "explosion" effect without continuous particles.
emitterLayer.birthRate = 1 // enabled
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
emitterLayer.birthRate = 0 // disabled — particles continue until lifetime expires
}
Lottie confetti is a simpler approach: load a ready-made .json file and play it once on top of the popup.
Animation Sequence
Everything shouldn't appear simultaneously — staggering creates a sense of "reward unfolding":
| Time | Event |
|---|---|
| 0ms | Backdrop appears, blur increases |
| 150ms | Card flies in from bottom (spring) |
| 300ms | Particle burst |
| 350ms | Icon scales up with bounce |
| 500ms | Haptic feedback (success) |
| 600ms | Reward text fades in |
| 800ms | Counter starts counting |
| 1200ms | CTA button appears |
On iOS — use DispatchQueue.main.asyncAfter for each stage or UIViewPropertyAnimator with addAnimations(afterDelay:). In Flutter — use AnimationController + Interval curves for each element.
Dismiss Animation
Closing should be fast: card exits downward in 0.25s with ease-in. Backdrop disappears simultaneously. No prolonged fade — users want to continue.
Timeline: 1 day for a standard reward popup with animations and haptic feedback on a single platform.







