Build an AI language tutor in FlutterFlow by connecting to the OpenAI API for conversation practice, storing vocabulary in Firestore with SM-2 spaced repetition scheduling fields, generating adaptive quizzes from due-for-review words, and tracking proficiency progress per skill. Use a Cloud Function as the AI proxy to keep your API key server-side. Avoid one-size-fits-all system prompts — tailor them to the user's proficiency level.
Why AI + Spaced Repetition Is the Most Effective Language Learning Combination
Language apps that use only AI conversation practice miss vocabulary retention — users enjoy the chat but forget words quickly. Apps that use only flashcards miss contextual fluency — users can recite words but cannot use them in conversation. The most effective learning design combines both: AI conversation for contextual exposure and production practice, plus spaced repetition (SM-2 algorithm) for systematic vocabulary retention. FlutterFlow can implement both with Firestore as the data layer — one document per vocabulary word per user, with SM-2 scheduling fields, and a Cloud Function proxying AI API calls.
Prerequisites
- A FlutterFlow project with Firebase Firestore connected and Authentication enabled
- An OpenAI API account with an API key (or Anthropic Claude API key)
- Firebase project on Blaze plan (Cloud Functions needed to proxy AI API calls securely)
- Basic understanding of Firestore document structure and FlutterFlow Action Flows
Step-by-step guide
Design the Firestore schema for vocabulary and progress
Design the Firestore schema for vocabulary and progress
Create two Firestore sub-collections under each user document. Collection 1: 'vocabulary' — one document per learned word with fields: word (String), translation (String), language (String), difficulty (Integer 1-5), interval (Integer, days until next review), easeFactor (Double, SM-2 ease factor, starts at 2.5), dueDate (Timestamp, next review date), repetitions (Integer, number of successful reviews), lastReviewedAt (Timestamp), exampleSentence (String). Collection 2: 'progress' — one document per skill area (vocabulary, grammar, listening, speaking) with fields: skillName (String), level (Integer 1-10), totalSessions (Integer), lastSessionAt (Timestamp), streakDays (Integer). This schema supports both spaced repetition scheduling and overall progress visualization.
Expected result: Firestore schema is configured in FlutterFlow with vocabulary and progress collections visible in the schema editor.
Build the AI conversation practice screen with adaptive prompts
Build the AI conversation practice screen with adaptive prompts
Create a 'Conversation Practice' page with a chat-style interface: a ListView of message bubbles (alternating user and AI), a TextField at the bottom for user input, and a Send button. Store the conversation history in a Page State variable (List of Maps with 'role' and 'content' keys). Create a Cloud Function named 'languageTutor' that accepts: userMessage (String), conversationHistory (List), userLevel (String: 'beginner', 'intermediate', 'advanced'), targetLanguage (String). The function builds a system prompt tailored to the user's level before calling OpenAI's chat completions API. Call this Cloud Function from a 'sendMessage' Custom Action in FlutterFlow. Add the AI response to the conversation history and scroll to the bottom of the ListView.
1// Cloud Function: languageTutor2// Proxies OpenAI calls with level-adaptive system prompts3const functions = require('firebase-functions');4const admin = require('firebase-admin');56const SYSTEM_PROMPTS = {7 beginner: `You are a friendly language tutor for complete beginners learning {language}.8- Use ONLY simple vocabulary (A1-A2 level)9- Keep sentences short (max 8 words)10- After every AI turn, provide a vocabulary tip: 'New word: [word] = [translation]'11- If the student makes an error, gently correct it once and move on12- Never use idioms or complex grammar`,13 intermediate: `You are a conversational language tutor for intermediate learners of {language}.14- Use B1-B2 vocabulary and natural sentence structures15- Engage in real conversation on everyday topics16- Correct grammar errors by repeating the correct form naturally in your response17- Introduce 1-2 new expressions per conversation turn`,18 advanced: `You are a challenging language tutor for advanced {language} learners.19- Use native-level vocabulary, idioms, and complex structures20- Discuss nuanced topics (culture, current events, abstract concepts)21- Provide corrections only for significant errors22- Challenge the learner with follow-up questions that require complex responses`,23};2425exports.languageTutor = functions.https.onCall(26 async (data, context) => {27 if (!context.auth) {28 throw new functions.https.HttpsError('unauthenticated', 'Login required');29 }30 const { userMessage, history, userLevel, targetLanguage } = data;31 const systemPrompt = (SYSTEM_PROMPTS[userLevel] || SYSTEM_PROMPTS.beginner)32 .replace(/{language}/g, targetLanguage);33 const messages = [34 { role: 'system', content: systemPrompt },35 ...history.slice(-10), // keep last 10 turns for context36 { role: 'user', content: userMessage },37 ];38 const apiKey = functions.config().openai.key;39 const response = await fetch('https://api.openai.com/v1/chat/completions', {40 method: 'POST',41 headers: {42 'Authorization': `Bearer ${apiKey}`,43 'Content-Type': 'application/json',44 },45 body: JSON.stringify({46 model: 'gpt-4o-mini',47 messages,48 max_tokens: 300,49 temperature: 0.7,50 }),51 });52 const json = await response.json();53 return { reply: json.choices[0].message.content };54 }55);Expected result: Sending a message calls the Cloud Function and displays an AI tutor response styled appropriately for the user's selected proficiency level.
Implement SM-2 spaced repetition scheduling
Implement SM-2 spaced repetition scheduling
Create a Custom Function named 'calculateSM2' that implements the SM-2 algorithm. It takes the current ease factor, interval, repetitions count, and user's quality rating (0-5, where 0=complete blackout, 5=perfect recall) and returns the new ease factor, interval, and due date. The SM-2 formula: if quality >= 3, the card is a pass — new interval = previous interval x ease factor (rounded up); new ease factor = old ease factor + 0.1 - (5-quality) x (0.08 + (5-quality) x 0.02). If quality < 3, reset interval to 1 day and repetitions to 0. After each vocabulary quiz answer, call this function and update the Firestore vocabulary document with the new scheduling values.
1// Custom Function: calculateSM22// Returns new spaced repetition scheduling values3Map<String, dynamic> calculateSM2(4 double easeFactor,5 int interval,6 int repetitions,7 int quality, // 0-5: 0=forgot, 3=correct with difficulty, 5=easy8) {9 double newEF = easeFactor;10 int newInterval;11 int newReps;1213 if (quality >= 3) {14 // Correct response15 if (repetitions == 0) {16 newInterval = 1;17 } else if (repetitions == 1) {18 newInterval = 6;19 } else {20 newInterval = (interval * easeFactor).round();21 }22 newReps = repetitions + 1;23 newEF = easeFactor +24 (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));25 if (newEF < 1.3) newEF = 1.3; // minimum ease factor26 } else {27 // Incorrect — reset28 newInterval = 1;29 newReps = 0;30 newEF = easeFactor; // ease factor unchanged on failure31 }3233 final dueDate = DateTime.now().add(Duration(days: newInterval));34 return {35 'easeFactor': newEF,36 'interval': newInterval,37 'repetitions': newReps,38 'dueDate': dueDate.toIso8601String(),39 };40}Expected result: After a quiz answer, the vocabulary document's interval, easeFactor, and dueDate fields update correctly — a perfect answer on a card reviewed twice should schedule it roughly 2 weeks out.
Build the vocabulary quiz from due-for-review words
Build the vocabulary quiz from due-for-review words
Create a 'Vocabulary Quiz' page. On page load, query Firestore: collection 'vocabulary' (sub-collection of current user) where dueDate <= now, ordered by dueDate ascending, limited to 20 words. Store the result as a Page State variable 'quizQueue' (list of vocabulary documents). Display the first word in the queue as a quiz card — show the word in the target language, wait for the user to tap 'Show Answer', then reveal the translation. Show 4 rating buttons: Forgot (0), Hard (3), Good (4), Easy (5) — matching SM-2 quality scores. On rating tap: call calculateSM2 Custom Function, update Firestore document with new scheduling values, remove the word from quizQueue, advance to the next card. When quizQueue is empty, show a completion screen with today's session stats.
Expected result: The quiz page shows due vocabulary cards, accepts ratings, and correctly schedules each word for future review based on SM-2 output.
Build the progress dashboard with skill tracking
Build the progress dashboard with skill tracking
Create a 'My Progress' page showing learning statistics. Add a streak counter at the top (query Firestore progress document, compute streak from lastSessionAt and current date). Add a vocabulary stats section: total words learned (count of vocabulary sub-collection documents), due for review today (count where dueDate <= now), mastered words (count where interval > 30 days). Add a simple bar chart or LinearProgressIndicator for each skill level (vocabulary, grammar, conversation). Below the stats, show the last 7 days of activity using a row of colored day indicators (green = studied, grey = missed). Update the progress document at the end of each quiz or conversation session.
Expected result: The progress page shows accurate counts from Firestore, an updated streak, and visual skill level indicators that reflect recent study sessions.
Complete working example
1// Spaced Repetition + Progress helpers2// Add to FlutterFlow Custom Code panel34import 'package:cloud_firestore/cloud_firestore.dart';5import 'package:firebase_auth/firebase_auth.dart';67// ─── SM-2 Algorithm ──────────────────────────────────────────────────────────8Map<String, dynamic> calculateSM2(9 double easeFactor,10 int interval,11 int repetitions,12 int quality,13) {14 double newEF = easeFactor;15 int newInterval;16 int newReps;1718 if (quality >= 3) {19 if (repetitions == 0) {20 newInterval = 1;21 } else if (repetitions == 1) {22 newInterval = 6;23 } else {24 newInterval = (interval * easeFactor).round();25 }26 newReps = repetitions + 1;27 newEF = easeFactor +28 (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));29 if (newEF < 1.3) newEF = 1.3;30 } else {31 newInterval = 1;32 newReps = 0;33 }34 final dueDate = DateTime.now().add(Duration(days: newInterval));35 return {36 'easeFactor': double.parse(newEF.toStringAsFixed(2)),37 'interval': newInterval,38 'repetitions': newReps,39 'dueDate': Timestamp.fromDate(dueDate),40 };41}4243// ─── Add new vocabulary word for current user ─────────────────────────────────44Future<void> addVocabularyWord(45 String word,46 String translation,47 String language,48 String exampleSentence,49) async {50 final user = FirebaseAuth.instance.currentUser;51 if (user == null) return;52 await FirebaseFirestore.instance53 .collection('users')54 .doc(user.uid)55 .collection('vocabulary')56 .add({57 'word': word,58 'translation': translation,59 'language': language,60 'exampleSentence': exampleSentence,61 'easeFactor': 2.5,62 'interval': 1,63 'repetitions': 0,64 'dueDate': Timestamp.now(),65 'createdAt': FieldValue.serverTimestamp(),66 });67}6869// ─── Update word after quiz answer ───────────────────────────────────────────70Future<void> updateWordAfterReview(71 String wordId,72 int quality,73 double currentEF,74 int currentInterval,75 int currentReps,76) async {77 final user = FirebaseAuth.instance.currentUser;78 if (user == null) return;79 final sm2 = calculateSM2(currentEF, currentInterval, currentReps, quality);80 await FirebaseFirestore.instance81 .collection('users')82 .doc(user.uid)83 .collection('vocabulary')84 .doc(wordId)85 .update({86 ...sm2,87 'lastReviewedAt': FieldValue.serverTimestamp(),88 });89}9091// ─── Compute current learning streak ─────────────────────────────────────────92int computeStreak(List<DateTime> sessionDates) {93 if (sessionDates.isEmpty) return 0;94 final sorted =95 sessionDates.toList()..sort((a, b) => b.compareTo(a));96 int streak = 0;97 DateTime check = DateTime.now();98 for (final date in sorted) {99 final diff = check100 .difference(DateTime(date.year, date.month, date.day))101 .inDays;102 if (diff <= 1) {103 streak++;104 check = DateTime(date.year, date.month, date.day);105 } else {106 break;107 }108 }109 return streak;110}Common mistakes when creating an AI-Based Language Learning App in FlutterFlow
Why it's a problem: Using the same AI system prompt for all user proficiency levels
How to avoid: Write distinct system prompts for beginner, intermediate, and advanced levels. Store the user's selected level in Firestore and pass it to every AI API call. Beginners need simple vocabulary, short sentences, and explicit vocabulary tips. Advanced users need natural complexity and nuanced challenges.
Why it's a problem: Storing all users' vocabulary words in a single top-level Firestore collection
How to avoid: Use Firestore sub-collections: users/{userId}/vocabulary/{wordId}. Sub-collection security rules are simpler: allow read, write: if request.auth.uid == userId. Queries automatically scope to the current user without needing a userId filter field.
Why it's a problem: Sending the full conversation history to the AI on every message
How to avoid: Keep a rolling window of the last 8-12 conversation turns for the AI context. For very long sessions, consider summarizing the earlier conversation into a single system message update ('So far you've practiced ordering food and asking for directions').
Best practices
- Let users self-select their proficiency level on onboarding and adjust it at any time — many users underestimate themselves (pick beginner when they're intermediate) which reduces engagement.
- Use AI to automatically detect vocabulary from conversation turns and suggest adding them to the user's spaced repetition deck with a one-tap 'Add to vocabulary' action.
- Implement a daily study goal (default: 10 vocabulary reviews + 5 minutes conversation) with a home screen widget showing today's progress toward the goal.
- Show the SM-2 ease factor as a visual difficulty indicator (easy, medium, hard) rather than a raw number — users should not see internal algorithm values.
- Use audio text-to-speech (tts_flutter package) to read vocabulary words and AI responses aloud — listening comprehension is a critical skill that text-only apps miss.
- Implement a vocabulary export feature so users can back up their word lists — this reduces the cost of switching apps and builds trust in your platform.
- Seed new users with 20-30 high-frequency starter words for their chosen language on first login so they have vocabulary to review immediately on day 1.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building a language learning app in FlutterFlow with spaced repetition vocabulary review. Each Firestore vocabulary document has: word, translation, easeFactor (Double), interval (Integer days), repetitions (Integer), dueDate (Timestamp). I need a Dart Custom Function that implements the SM-2 algorithm. Given quality (0-5), current easeFactor, interval, and repetitions, return the new easeFactor, interval, repetitions, and dueDate Timestamp. Include the SM-2 formula and edge cases (minimum easeFactor of 1.3, reset on quality < 3).
I have a FlutterFlow AI language tutor app. My Cloud Function 'languageTutor' accepts: {userMessage, history (last 10 turns), userLevel ('beginner'/'intermediate'/'advanced'), targetLanguage}. I want to add a feature where the AI automatically detects any vocabulary words it introduces and returns them as a separate JSON array in its response, so my app can offer a one-tap 'Add to vocabulary deck' button. How should I modify the system prompt and response format to reliably extract new vocabulary words from the AI's reply?
Frequently asked questions
Which AI model should I use for the language tutor — GPT-4o, GPT-4o-mini, or Claude?
For language tutoring, GPT-4o-mini provides the best cost-to-quality ratio. It is significantly cheaper than GPT-4o while maintaining high quality for conversational language tasks. Claude Haiku is similarly priced and performs well for language tasks. Reserve GPT-4o or Claude Sonnet for advanced users who need more nuanced cultural explanations or complex grammar analysis.
How many vocabulary words can I store per user before Firestore costs become significant?
Firestore charges per document read, not per collection size. A user reviewing 20 vocabulary words per day performs 20 reads and 20 writes per day — less than $0.01/month at standard pricing. Even a user with 5,000 vocabulary words who reviews 50 per day would cost under $0.05/month in Firestore operations. The cost scales with usage, not storage size.
How do I prevent users from gaming the spaced repetition system by always clicking 'Easy'?
The SM-2 algorithm naturally handles this — clicking 'Easy' every time increases the interval exponentially, scheduling reviews months or years out. Users who mark everything as easy stop seeing vocabulary in their daily reviews and lose retention. You can add a 'test yourself' mode that shows the word before the translation to encourage honest self-assessment.
Can I support multiple target languages for the same user in one app?
Yes. Add a 'language' field to each vocabulary document and a 'selectedLanguage' App State variable. Filter all vocabulary queries by language: where('language', isEqualTo: selectedLanguage). The progress sub-collection should also be keyed by language (e.g., 'progress/spanish', 'progress/french') so each language has independent progress tracking.
How do I add audio pronunciation to vocabulary cards?
Use the tts_flutter or flutter_tts package to synthesize text-to-speech for vocabulary words. Call tts.speak(word) when the vocabulary card is displayed. For higher-quality pronunciation, pre-generate audio files using OpenAI's TTS API or Google Text-to-Speech, store them in Firebase Storage, and play them with the audioplayers package.
What is a realistic daily active user session time for a language learning app?
Successful language learning apps (Duolingo, Babbel) target 5-15 minutes of daily active use. Design your default daily goal around this: 10 vocabulary reviews (2-3 minutes) + one AI conversation session (5-7 minutes). Apps that demand 30+ minutes per day see much higher drop-off rates. A short daily habit is more effective for language acquisition than occasional long sessions.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation