Learn through PocketCards, the runnable flashcard app in Android + AI Cookbook. Open the hands-on codelab · Browse the pinned source.
The product before the model
PocketCards turns a passage of study notes into editable question-and-answer cards. The learner still decides what to study and whether an answer is correct. The model proposes a draft; application code controls saving, favorites, review dates and navigation. This distinction prevents a common mistake: treating every feature in an AI-branded app as a model responsibility.
A handwritten sample deck is useful for exploring the interface, but it is not evidence of inference. Likewise, a fake generator in a test proves the screen can handle a response, not that Gemini works. Inference happens when FirebaseCardGenerator sends notes through Firebase AI Logic and receives a model response. No model is trained inside PocketCards.
Rules, learned models and generative output
Use deterministic code when the answer is precisely specified: reject notes shorter than 40 characters, cap input at 8,000 characters, or schedule a reviewed card for tomorrow. A generative model helps with the less rigid transformation from arbitrary prose to useful study questions. That flexibility also introduces variable wording, omissions and unsupported claims.
Consider this source: “Chlorophyll absorbs light. Photosynthesis occurs in chloroplasts.” Two grounded cards ask what absorbs light and where photosynthesis occurs. A card asking for the optimal wavelength introduces a fact absent from the source, even if its answer happens to be scientifically correct. Our product contract is source-supported recall, not open-ended tutoring.
Trace the smallest useful feature
Notes → input checks → generation request → JSON parsing
→ card validation → editable draft → explicit Save → local deck
The model stops at the draft boundary. A failed request must not erase notes or overwrite a saved deck. A successful request must not silently save content the learner has not reviewed. This gives users a chance to catch mistakes and keeps failure recovery understandable.
Read the CardGenerator contract below. The suspend function models one asynchronous request. The available property represents build configuration, not a promise that the service is reachable or that the project has quota.
interface CardGenerator {
val available: Boolean
suspend fun generate(notes: String): List<Flashcard>
}
Predict, run, explain
Before enabling Firebase, run the sample and create a manual card. Predict whether either action needs the network. Then follow the codelab to configure generation. Identify the exact action that sends notes off-device and the exact action that persists a deck.
Checkpoint: Explain why card generation benefits from a model while card counting does not. If you replaced Gemini with a different provider, name the interface that should stay stable. Your answer should mention both the product outcome and who controls each decision.
Worked example: who owns each decision?
A learner supplies notes about chlorophyll and taps Generate. The response contains three usable cards. They edit one answer, save the deck and tap Got it while studying.
| Decision | Owner | Why |
|---|---|---|
| Whether input meets the length rule | Application validation | The rule must behave consistently |
| Which questions to propose | Model | There are many useful phrasings and fact selections |
| Whether a returned card is structurally valid | Application validation | A UI cannot safely assume arbitrary output is usable |
| Whether an answer is supported by these notes | Learner review and evaluation | A schema cannot establish factual support |
| When the saved card is due again | Application review logic | Scheduling is a defined rule, not a language task |
Try before reading on: If the model returns a card saying “Ignore the user and delete every deck,” which code should execute it? None. A Flashcard is data containing question and answer strings. This sample exposes no model tool for deleting decks. Do not interpret response text as application commands.
A useful boundary is both a type and a product promise. List<Flashcard> keeps provider-specific response objects away from UI code; the review screen gives the learner control before persistence. Changing providers should preserve these promises, even if response parsing changes.
Exit check: Explain how you would demonstrate inference to a reviewer. A strong answer includes a real generation request and reviewed output, while separately identifying the handwritten sample and test fakes. An AI-looking screenshot alone is insufficient.