Inspiration
I have about four hundred screenshots of lecture slides on my phone. I've opened maybe six of them.
That is the actual problem. Capturing something is easy and feels productive, and it does almost nothing for memory. The part that works, going back to the material right before you would have forgotten it, is the part nobody does, because deciding when to review is tedious and easy to skip.
So the app makes that decision for you. You capture something once, or describe a topic and let it build the set, and Interval brings it back on a widening schedule: 1 day, 3, 7, 30, 90. Recall it and the gap widens. Miss it and it comes straight back tomorrow.
The honest origin story: this started as a team of four. Nobody started. With three days left I decided that submitting something working alone beat submitting nothing together, and only then the team took action. We had never written a line of Kotlin before this week.
What it does
Three question types over one scheduler. Flashcards you grade yourself, plus multiple choice and true/false that grade themselves. They share one model, so the ladder, the statistics and the storage never branch on type.
AI in three places, each tied to a real moment: 1) Generate - name a topic or paste your notes and get a mixed study set back. Nothing is saved until you tick it, so a hallucinated fact costs one tap to reject rather than sitting in your rotation for a month. 2) Explain this - appears the instant you get a card wrong, which is the one moment you actually want an explanation. 3) Coach - a chat tab for everything else.
It works with Gemini, OpenAI or OpenRouter. You bring your own key; the app never ships one.
Notifications that are the product, not a retention tactic. A card coming due arms the alarm. Tapping the notification opens straight into that session.
A reward loop with a punchline. Correct answers pay XP, streaks pay more, and there is a milestone ladder. The top tier is at one million XP and reads: "the developer will genuinely buy you a five dollar lunch. Anywhere you like. Just email him. Nobody has ever emailed him." At ten to twenty XP a card that is north of fifty thousand correct answers, so it is a promise I can actually afford to keep.
How we built it
Native Android - Kotlin, Jetpack Compose, Material 3, minSdk 26.
The whole app has exactly one external dependency: the RevenueCat SDK. Everything else is framework: | Need | What I Used | Instead Of | | :--- | :--- | :--- | | Storage | org.json` + single file (atomic writes) | Room | | Background Work | AlarmManager | WorkManager | | Navigation | Sealed interface + when | navigation-compose | | AI HTTP Calls | HttpURLConnection | Retrofit / OkHttp / Ktor | | Visuals & Assets | Compose Canvas | Icon packs & charting libraries |
That was a deliberate constraint, not minimalism for its own sake. As someone who had never used Kotlin, we could not debug a Gradle version conflict quickly. Every dependency is a version to keep compatible and a failure mode to diagnose, and on a three-day clock the framework version wins every time. It also means anyone can clone the repo and it just builds.
Monetization. One entitlement, one flag. Billing.isPro is the only thing any gated feature reads, scattering purchase checks through the UI is how an app ends up with one screen that thinks you paid and another that doesn't. Pro is defined as any active entitlement rather than a hardcoded string, so renaming something in the RevenueCat dashboard cannot silently lock a paying customer out. The paywall renders whatever packages the default offering contains, so prices change server-side with no app update.
The detail we are most pleased with: the local profile's id is passed to Purchases.logIn(), so a purchase belongs to the person, not to the install. Delete the app, reinstall, and Pro comes back.
Challenges we ran into
Shipping was harder than building. Google Play requires new personal developer accounts to run a closed test with twelve testers opted in for fourteen continuous days before you can even apply for production. The Galaxy Store now requires corporate seller status for Android content. Apple needs a Mac I don't have. Working that out early is why we targeted Next Gen and used RevenueCat's Test Store, which gave me a fully working purchase flow with no store account at all.
Three AI providers, because the first two fought me. Gemini kept rejecting model ids that the documentation said were valid, and the failure looked identical to a bad key. Rather than keep guessing names, we added a Find models button that asks the provider which models that specific key can actually call and lists them. That turned an opaque dead end into a working picker, and it is better for anyone cloning the repo too.
A blurred orb, not a shader. The loading animation we wanted was a WebGL shader orb. There is no GLSL here, so it is six coloured lobes on three co-prime orbits drawn to a Canvas, run through a real Gaussian blur so their edges dissolve into each other, with a crisp unblurred pass on top carrying the rim and a specular highlight. That last part matters, without it a blurred blob just reads as an out-of-focus mistake.
Bugs that taught me something. A card promoted to the three-day rung rendered as "Due in 2 d", because integer division floored three-days-minus-two-seconds. The undo snackbar spawned exactly on top of the grade buttons and blocked the next card. Neither is clever; both are the kind of thing you only find by using your own app.
Accomplishments that we're proud of
It exists. Three days ago we had never written a line of Kotlin, and the team this was meant to be had not started. Choosing to build something small and finished alone, instead of something ambitious and broken together, is the decision the whole project rests on.
One external dependency. The RevenueCat SDK, and nothing else. Storage is org.json and a file written atomically, scheduling is AlarmManager, navigation is a sealed interface and a when, HTTP to three different AI providers is HttpURLConnection, and every icon, chart and animation is Compose Canvas. That started as a survival tactic, we couldn't debug a Gradle conflict quickly, but it ended up as the thing I'd defend hardest. Clone the repo and it builds, first try, with no setup.
Purchases belong to the person, not the install. The local profile's id is passed to Purchases.logIn(), so deleting the app and reinstalling brings Pro back without a restore tap. It's three lines and most hackathon integrations skip it.
Turning a dead end into a feature. Gemini kept rejecting model ids the docs said were valid, and the failure looked identical to a bad API key. Instead of guessing names for another hour, we added a Find models button that asks the provider which models your specific key can actually call. That fixed my problem and made the app better for everyone who clones it.
An orb with no shader. The loading animation we wanted was WebGL. There's no GLSL here, so it's six coloured lobes on three co-prime orbits drawn to a Canvas, run through a real Gaussian blur so their edges dissolve, with a crisp pass on top for the rim and specular. Working out why the first version looked wrong, a blurred blob with no edge reads as a mistake, not a material, taught me more than the code did.
Knowing what to cut. we scoped Kotlin Multiplatform, OneSignal push, and OCR card generation, and dropped all three. Each was chasing a prize category that required a store release we structurally could not get. Cutting them is the only reason the parts that shipped actually work.
What we learned
That most of the difference between an app that feels amateur and one that doesn't is motion. Halfway through we replaced every fixed-duration animation with a spring. A tween always arrives at the same moment no matter how far it travelled, which is exactly what makes an interface feel mechanical. we also redefined the app's tap modifier so every surface dips under your finger, one edit, and the whole app stopped feeling like rectangles with listeners attached.
And that scope is a skill. we planned Kotlin Multiplatform, OneSignal push, and OCR card generation. we cut all three, each was chasing a prize category that required a store release we could not get, and cutting them is the reason the parts that shipped actually work.
What's next for Interval
Sync, card editing from inside a review, and real PDF and image parsing, the app currently accepts those attachments and says plainly that it is only using the filename as a hint, rather than pretending it read the page.
Built With
- alarmmanager
- android
- google-gemini
- jetpack-compose
- kotlin
- kotlin-coroutines
- material-design
- openai
- openrouter
- revenuecat
Log in or sign up for Devpost to join the conversation.