# How to Build a Personalized Fitness Plan Generator in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 45-60 min
- Compatibility: FlutterFlow Free+ (Cloud Functions require Firebase Blaze plan)
- Last updated: March 2026

## TL;DR

Create an AI-powered fitness planner by collecting user stats and goals in an assessment form, sending that data to an AI API via a Cloud Function with a personal trainer system prompt, and displaying the returned plan in a TabBar with one tab per day. Add workout logging and a regeneration button for progressive overload. Always include a medical disclaimer.

## What You Are Building and Why

A generic workout plan app is easy to build. A personalized one that adapts to each user's body, goals, and available equipment is genuinely useful — and a strong differentiator in the fitness app market. FlutterFlow lets you build the entire UI visually, while a Firebase Cloud Function handles the AI API call so your API key never touches the client. The result is a polished app that feels custom-built for each user, with workout logs that feed back into the next plan generation cycle to keep the training progressive. This tutorial builds the full loop: assess → generate → log → regenerate.

## Before you start

- A FlutterFlow project connected to Firebase (Blaze plan for Cloud Functions)
- An AI API key (OpenAI GPT-4o or Anthropic Claude) stored as a Firebase Function environment secret
- Firestore collections: users, fitness_plans, and workout_logs
- Basic knowledge of FlutterFlow forms and Page State variables

## Step-by-step guide

### 1. Build the fitness assessment form

Create a new page called FitnessAssessmentPage. Add a ScrollView containing a Column with the following input widgets: a Slider for age (16–75), two TextFields for height (cm) and weight (kg), a DropdownButton for primary goal (options: Lose Weight, Build Muscle, Improve Endurance, Maintain Fitness), a Multi-select chip widget listing available equipment (Bodyweight Only, Dumbbells, Barbell, Resistance Bands, Full Gym), and a ToggleButtons row for days per week (3, 4, 5, or 6). Bind each widget to a Page State variable of the appropriate type. Add a prominent medical disclaimer Text widget at the bottom of the form in red: 'Consult your doctor before starting any new exercise program, especially if you have a medical condition, injury, or are new to exercise.' Add a Checkbox that must be ticked to enable the 'Generate My Plan' button.

**Expected result:** A complete scrollable assessment form with all fields, the medical disclaimer, the consent checkbox, and a disabled 'Generate My Plan' button that enables only when the checkbox is ticked.

### 2. Set up the Cloud Function with AI API call

In your Firebase project, create a Cloud Function called generateFitnessPlan. It accepts a JSON body with fields: age, heightCm, weightKg, goal, equipment (array), daysPerWeek, and optionally previousPlanSummary and recentLogs for progressive regeneration. Build a detailed system prompt that positions the AI as a certified personal trainer. Construct a user message summarising the assessment data. Call your AI API (OpenAI or Anthropic) with the system prompt and user message, requesting a structured JSON response with an array of day objects, each containing dayName, focus, and an exercises array with name, sets, reps, rest, and notes. Return the parsed JSON to the Flutter client. Store your API key using Firebase Functions config or Secret Manager — never hardcode it.

```
// functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const axios = require('axios');
admin.initializeApp();

exports.generateFitnessPlan = functions.https.onCall(async (data, context) => {
  if (!context.auth) throw new functions.https.HttpsError('unauthenticated', 'Login required');

  const { age, heightCm, weightKg, goal, equipment, daysPerWeek, previousPlanSummary } = data;
  const apiKey = process.env.OPENAI_API_KEY;

  const systemPrompt = `You are a certified personal trainer with 10 years of experience.
Create safe, progressive workout plans tailored to the individual.
Always prioritize proper form and injury prevention.
Return ONLY valid JSON matching this schema:
{"days": [{"dayName": string, "focus": string, "exercises": [{"name": string, "sets": number, "reps": string, "rest": string, "notes": string}]}]}`;

  const userMessage = `Create a ${daysPerWeek}-day/week workout plan for:
- Age: ${age}, Height: ${heightCm}cm, Weight: ${weightKg}kg
- Goal: ${goal}
- Available equipment: ${equipment.join(', ')}
${previousPlanSummary ? '- Previous plan: ' + previousPlanSummary + ' (increase intensity by 5-10%)' : ''}`;

  const response = await axios.post('https://api.openai.com/v1/chat/completions', {
    model: 'gpt-4o',
    messages: [{ role: 'system', content: systemPrompt }, { role: 'user', content: userMessage }],
    response_format: { type: 'json_object' },
  }, { headers: { Authorization: `Bearer ${apiKey}` } });

  const plan = JSON.parse(response.data.choices[0].message.content);
  return plan;
});
```

**Expected result:** Deploying the function succeeds. Calling it from the Firebase console returns a valid JSON fitness plan with the correct number of day objects.

### 3. Call the Cloud Function from FlutterFlow and store the plan

Create a Custom Action called callGenerateFitnessPlan. It reads all Page State variables from the assessment form and calls FirebaseFunctions.instance.httpsCallable('generateFitnessPlan') with them as a map. On success, save the returned plan JSON to a new Firestore document in the fitness_plans collection with the current user's UID, a createdAt timestamp, and a planVersion integer starting at 1. Also save the plan JSON to an App State variable called currentPlan so you can display it immediately without a Firestore read. Navigate to the PlanViewPage. Wire this action to the 'Generate My Plan' button. Show a full-screen loading overlay with an animated logo while the AI generates the plan — this typically takes 3–8 seconds.

**Expected result:** Tapping 'Generate My Plan' shows a loading screen, then navigates to PlanViewPage with the generated plan displayed.

### 4. Display the plan in a day-by-day TabBar

Create PlanViewPage. Retrieve the currentPlan from App State. The plan's days array length determines the number of tabs — use a Custom Widget or FlutterFlow's Tab Bar widget with dynamic tab count. Each tab label is the dayName field (e.g., 'Monday - Push', 'Wednesday - Pull'). Inside each tab, add a Text widget for the focus description, then a ListView of exercise cards. Each card shows the exercise name in a bold Text, and a Row with chips for sets x reps, rest time, and a small info icon that opens a Popup showing the notes field. Add a 'Mark All Complete' button at the bottom of each tab that logs the day's workout to Firestore.

**Expected result:** PlanViewPage shows a TabBar with one tab per training day. Each tab displays the exercises for that day in a scrollable card list.

### 5. Implement workout logging and progressive overload regeneration

When the user taps 'Mark All Complete' on a day tab, create a Firestore document in the workout_logs collection with fields: userId, planId, dayName, completedAt, and exercisesCompleted (the full exercise list). After logging, check if all days in the current plan have been completed within the last 7 days. If so, show a Congratulations Dialog with two buttons: 'Keep This Plan' and 'Generate Progressive Plan'. The 'Generate Progressive Plan' button calls callGenerateFitnessPlan again but this time passes previousPlanSummary — a string summarising the completed plan's exercises and volumes. The Cloud Function uses this to increase intensity by 5–10% in the new plan. Increment planVersion in Firestore so users can browse their plan history.

**Expected result:** Completing all workout days unlocks a 'Generate Progressive Plan' button. The new plan is visibly harder (more sets, heavier weights, reduced rest times) than the previous one.

## Complete code example

File: `callGenerateFitnessPlan.dart`

```dart
import 'package:cloud_functions/cloud_functions.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

Future<Map<String, dynamic>?> callGenerateFitnessPlan({
  required int age,
  required double heightCm,
  required double weightKg,
  required String goal,
  required List<String> equipment,
  required int daysPerWeek,
  String? previousPlanSummary,
}) async {
  final user = FirebaseAuth.instance.currentUser;
  if (user == null) return null;

  // Call the Cloud Function
  final callable = FirebaseFunctions.instance.httpsCallable(
    'generateFitnessPlan',
    options: HttpsCallableOptions(timeout: const Duration(seconds: 60)),
  );

  final result = await callable.call({
    'age': age,
    'heightCm': heightCm,
    'weightKg': weightKg,
    'goal': goal,
    'equipment': equipment,
    'daysPerWeek': daysPerWeek,
    if (previousPlanSummary != null) 'previousPlanSummary': previousPlanSummary,
  });

  final planData = Map<String, dynamic>.from(result.data as Map);

  // Persist to Firestore
  final planRef = await FirebaseFirestore.instance
      .collection('fitness_plans')
      .add({
    'userId': user.uid,
    'plan': planData,
    'goal': goal,
    'daysPerWeek': daysPerWeek,
    'createdAt': FieldValue.serverTimestamp(),
    'planVersion': 1,
    'completedDays': [],
  });

  // Return plan with its Firestore ID
  planData['planId'] = planRef.id;
  return planData;
}

// Log a completed workout day
Future<void> logWorkoutDay({
  required String planId,
  required String dayName,
  required List<dynamic> exercises,
}) async {
  final user = FirebaseAuth.instance.currentUser;
  if (user == null) return;

  await FirebaseFirestore.instance.collection('workout_logs').add({
    'userId': user.uid,
    'planId': planId,
    'dayName': dayName,
    'exercisesCompleted': exercises,
    'completedAt': FieldValue.serverTimestamp(),
  });

  // Mark day complete on the plan document
  await FirebaseFirestore.instance
      .collection('fitness_plans')
      .doc(planId)
      .update({
    'completedDays': FieldValue.arrayUnion([dayName]),
  });
}
```

## Common mistakes

- **Generating a fitness plan with no medical disclaimer or consent gate** — Users with heart conditions, recent surgeries, injuries, or other medical issues could follow a high-intensity plan and seriously injure themselves. Beyond user safety, this is also a liability issue for the app developer. Fix: Add a visible medical disclaimer and a mandatory checkbox consent gate before the user can generate a plan. Log that consent with a timestamp to Firestore alongside the plan document.
- **Calling the AI API directly from the Flutter client instead of via a Cloud Function** — Your AI API key will be visible in the compiled app binary and can be extracted using standard reverse-engineering tools. Anyone who finds your key can generate unlimited AI calls charged to your account. Fix: Always proxy AI API calls through a server-side Cloud Function. Store the API key in Firebase Secret Manager or Functions environment config, never in client-side code or Firestore.
- **Not requesting JSON format from the AI API** — Without explicit JSON format instructions and response_format: { type: 'json_object' } (for OpenAI), the AI may return a plan as unstructured prose, causing your JSON.parse() call to throw an error and the plan not to display. Fix: Always pass response_format: { type: 'json_object' } for OpenAI, or include explicit JSON schema in your system prompt for other providers. Add a try-catch around JSON.parse() and return a helpful error message if parsing fails.

## Best practices

- Always include a medical disclaimer and require checkbox consent before generating any fitness plan
- Store AI API keys exclusively in server-side environment variables, never in Flutter code or Firestore
- Set a Cloud Function timeout of at least 60 seconds — AI generation can occasionally take 20–30 seconds
- Archive all previous plan versions in Firestore so users can compare their progression over time
- Include exercise notes explaining correct form — reducing injury risk improves retention
- Validate the AI response schema before rendering — use a fallback message if a field is missing
- Add a difficulty rating widget on each exercise so users can give feedback for the next regeneration
- Cache the current plan locally in App State to avoid reloading from Firestore every time the user opens the plan

## Frequently asked questions

### Which AI API works best for generating fitness plans in FlutterFlow?

OpenAI's GPT-4o is the most reliable for structured JSON output because it supports the response_format parameter natively. Anthropic Claude 3.5 Sonnet is an excellent alternative and often produces more nuanced exercise progressions. Both work well in a Firebase Cloud Function proxy pattern.

### Can I generate the plan without a Cloud Function by calling the AI API from Flutter directly?

Technically yes, but you must never do this in production. Calling an AI API from Flutter code means your API key is embedded in the app binary and can be stolen. Always use a server-side proxy such as a Firebase Cloud Function.

### How do I handle the 3-8 second AI generation delay without losing users?

Show a full-screen loading overlay with an animated progress indicator and rotating motivational text. Users tolerate longer waits for personalised content, but they need visual feedback that something is happening. A 'Your plan is being built' message with an animation keeps engagement high.

### What happens if the AI returns a plan that is unsafe for a user's medical condition?

Your app cannot medically screen users, so the medical disclaimer and consent checkbox are essential. You should also include low-intensity modification notes in the system prompt and recommend consulting a doctor. Consider adding a 'I have an injury or medical condition' checkbox that adjusts the prompt to request a gentler, rehabilitation-focused plan.

### How do I implement progressive overload automatically?

After the user completes the full plan cycle, pass a previousPlanSummary string to the Cloud Function containing the exercises, sets, and reps from the last plan. Include an instruction in the system prompt such as 'increase volume by 5-10% from the previous plan'. The AI will generate slightly more demanding exercises, replicating what a real trainer would do.

### Can I add video demonstrations for each exercise?

Yes. The simplest approach is to maintain a Firestore collection of exercises with a videoUrl field. When rendering each exercise card, look up the exercise name in that collection and display a video thumbnail. Tapping the thumbnail opens an in-app video player. You can pre-populate this collection with links to public YouTube exercise demonstration videos.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-personalized-fitness-plan-generator-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-personalized-fitness-plan-generator-in-flutterflow
