Learning Breaks for game developers
Swap your interstitial for a learning break in about ten minutes. Your AdMob (or any other) ad stays as the fallback, so a player without Learning Breaks sees exactly what they see today.
What a learning break is
At a natural pause, where you would show an interstitial, a child with Learning Breaks answers a short question or two from material a parent chose. It opens in the Learning Breaks app's own screen, on top of your game, and always ends within the time you allow. Then your game carries on.
You are paid for breaks shown to your players. You never see what was asked, what was answered, or whether it was right.
1. Add the dependency
// build.gradle.kts (app)
dependencies {
implementation("com.learningbreaks:breaksdk:1.0.0")
}
The SDK declares the <queries> entry it needs, and it merges into your
manifest on its own. Once your app is registered (section 6), add your publisher id:
<!-- AndroidManifest.xml, inside <application> -->
<meta-data android:name="com.learningbreaks.PUBLISHER_ID" android:value="pub_..." />
The SDK sends it with every request. Without it, only a debug build can get breaks, and only on a phone with developer mode on (section 6).
2. Replace your interstitial
The SDK has the same two-step shape as an interstitial ad: load during play, show at the break.
/** A learning break when one is available, otherwise your normal interstitial. */
class BreakOrAd(private val activity: ComponentActivity, private val adUnitId: String) {
private val breaks = LearningBreaks(activity)
private var grant: BreakGrant? = null
private var ad: InterstitialAd? = null
private var resume: () -> Unit = {}
// Create BreakOrAd in onCreate: registerForActivityResult must run before the screen starts.
private val showBreak = activity.registerForActivityResult(ShowBreak()) { resume() }
/** When a round starts. Nothing waits on this. */
fun preload() {
activity.lifecycleScope.launch {
grant = breaks.load(placement = "between-rounds", maxSeconds = 60)
if (grant == null && ad == null) loadAd() // only when there is no break
}
}
/** When the round is over. [then] runs when the game may carry on. */
fun showAtBreak(then: () -> Unit) {
resume = then
grant?.also { grant = null }?.let { if (breaks.show(showBreak, it)) return }
ad?.also { ad = null }?.let { interstitial ->
interstitial.fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() = then()
override fun onAdFailedToShowFullScreenContent(e: AdError) = then()
}
interstitial.show(activity)
return
}
then() // nothing to show: carry on
}
private fun loadAd() = InterstitialAd.load(activity, adUnitId, AdRequest.Builder().build(),
object : InterstitialAdLoadCallback() {
override fun onAdLoaded(loaded: InterstitialAd) { ad = loaded }
override fun onAdFailedToLoad(error: LoadAdError) { ad = null }
})
}
In your game:
breakOrAd = BreakOrAd(this, adUnitId = "ca-app-pub-xxx/yyy") // in onCreate
fun onRoundStarted() = breakOrAd.preload()
fun onRoundOver() = breakOrAd.showAtBreak { startNextRound() }
Before and after. If you already have interstitial code, the whole change is
this: InterstitialAd.load(...) moves inside if (grant == null), and show
tries the break first.
Why load the ad only after a miss. An ad loaded and never shown lowers your show rate in AdMob. Asking for a break first costs nothing.
No break is normal
load returns null and show returns false whenever there is no break:
Learning Breaks isn't installed, no child is set up, there's nothing to ask, the
parent has turned breaks down, or your app isn't registered. All of those
get the same answer: show your ad or carry on. Never retry in a loop.
Where to put breaks
- Do: between rounds, after a level, on a results screen.
- Don't: mid-play, during a timer, or where a break could cost the player progress.
maxSeconds is the longest pause you can give. A break always ends within it.
If your pause is shorter than a break needs, load returns null.
3. Handle the result
The ShowBreak callback gets a BreakResult with an outcome:
| Outcome | Meaning | What to do |
|---|---|---|
COMPLETED |
The child finished the break | Carry on |
RELEASED |
The child took part and the time limit ended it | Carry on |
ABANDONED |
The break didn't really happen | Carry on |
For a scheduled break the answer is always "carry on". Never penalise a child for any outcome.
If your process was killed while the break was open, the callback may never
arrive. Keep the grant and ask later with breaks.outcomeOf(grant).
4. Rewarded: "practice to earn" instead of "watch to earn"
private val showRewardBreak = registerForActivityResult(ShowBreak()) { result ->
if (result.outcome == BreakOutcome.COMPLETED) giveReward()
// RELEASED: the child tried, then stopped. "Good try!" - no reward, no penalty.
if (result.outcome != BreakOutcome.ABANDONED) roundsSinceBreak = 0
}
fun prepareEarnButton() = lifecycleScope.launch {
rewardGrant = breaks.loadReward(placement = "earn-coins")
if (rewardGrant != null) earnButton.isVisible = true
else RewardedAd.load(this@GameActivity, rewardedUnitId, AdRequest.Builder().build(),
object : RewardedAdLoadCallback() {
override fun onAdLoaded(ad: RewardedAd) { rewardedAd = ad; earnButton.isVisible = true }
})
}
earnButton.setOnClickListener {
rewardGrant?.let { if (breaks.show(showRewardBreak, it)) return@setOnClickListener }
rewardedAd?.show(this) { giveReward() }
}
Rules for reward breaks:
- Show the earn button only when a grant or an ad is ready.
- Give the reward only on
COMPLETED. A reward break finishes on right answers only. - Reset your "break every X rounds" counter after
COMPLETEDorRELEASED. Learning Breaks also holds back scheduled breaks for your game for a few minutes afterwards. - Parents set a daily limit on reward breaks for each game. Past it,
loadRewardreturnsnull.
5. The one-time intro (optional, recommended)
// On your title screen, once it is showing (main thread):
lifecycleScope.launch { breaks.showIntroIfNeeded(this@TitleActivity) } // true = something is on screen
- Learning Breaks installed: it shows its own short "how breaks work" intro, once per device, across every game. It also shows it before a child's first break, so skipping this call is fine. The intro counts inside that first break's time limit.
- Not installed: the SDK shows a small card, "This game can give learning breaks instead of ads. Ask a grown-up." The store link sits behind a grown-up check. It appears at most once per install, and later calls do nothing.
- Installed but not ours, or too old: nothing is shown.
- Your control: set
breaks.introCardEnabled = falseto never show the card. You can't change its wording or show it more than once. - Activity results: the intro opens with
startActivityForResultusingLearningBreaks.INTRO_REQUEST_CODE. If your activity overridesonActivityResult, ignore that code. - Diagnostics: every call reports
INTRO_SHOWNorINTRO_SKIPPED, with the reason.
6. Testing and going live
-
Debug builds work without registering. Install Learning Breaks and set up a child. Then turn on Grown-ups > Developer mode on your test phone. Breaks will then appear in any debuggable build. Leave developer mode off on children's phones.
-
When a break doesn't appear, turn on diagnostics:
breaks.diagnosticListener = BreakDiagnosticListener { Log.d("Breaks", it.toString()) }They tell you whether Learning Breaks is installed and trusted, whether it connected, and whether a break was available. They never tell you why it refused. The parent sees that in Learning Breaks under Grown-ups > Break activity.
-
Release builds must be registered. Sign in at learningbreaks.com:
-
Complete your publisher profile, including your domain. We review it.
-
Register your package and signing certificates (SHA-256). You'll get a publisher id (
pub_...) for the manifest entry in section 1. -
Prove the app and the domain are yours. Serve these two files on your domain; the portal shows their exact contents:
/.well-known/assetlinks.json, listing your package and certificates;/.well-known/learningbreaks.json, listing your publisher ids:{"publisher_ids":["pub_..."]}. Add each new app's id here too.
Both files are checked against the domain we verified. Changing your domain only takes effect once we've reviewed the change.
We review every app. Unregistered release builds get no breaks, and your developer dashboard lists every refusal, with how to fix it.
-
-
Get listed. Approved games can add a listing that appears to parents in Learning Breaks under "Games that work with Learning Breaks".
What you receive, and what you don't
You receive:
- the outcome (
COMPLETED,RELEASED,ABANDONED); - an opaque token that means nothing to you;
- a monthly statement of breaks and earnings.
You never receive:
- the question, the answer, or whether it was right;
- the subject;
- the child's name, age, or profile;
- how long the break took.
Breaks open in Learning Breaks' own screen, so that material never enters your process. The SDK itself carries no learning content.
Your game's data safety form. The SDK makes no network calls of its own and collects nothing from the player. It only connects to Learning Breaks on the device.
How you are paid
- Where the money comes from: each subscribing family's fee.
- How it's split: a share goes into a monthly pool, divided among games in proportion to the breaks each one delivered to that family's children.
- What counts: a break counts when the child took part (
COMPLETEDorRELEASED).ABANDONEDbreaks don't count. Repeat breaks to the same child in a short time count for less, so extra breaks don't pay extra. - Payouts: made through Stripe once your identity and tax details are verified.
Rates and terms are in the publisher agreement.