# How to Add an Interactive Recipe Cook Along to Your App (Copy-Paste Prompts Included)

- Tool: App Features
- Last updated: July 2026

## TL;DR

An interactive cook-along needs a full-screen step-by-step UI with large text, per-step countdown timers that fire background notifications, a WakeLock to keep the screen on, and a serving size scaler. With FlutterFlow you can ship a working native cook-along mode in 4–8 hours for $0–$25/month — all timers and voice recognition run on-device at no per-call cost. iOS background timer behavior is the main gotcha that breaks first builds.

## What an Interactive Recipe Cook Along Feature Actually Is

A cook-along mode transforms a standard recipe page into a guided cooking session: the app moves through each step one at a time, shows a countdown timer for steps that require it, keeps the phone screen awake so the user is not touching the screen with floury hands, and listens for voice commands to advance steps hands-free. The feature is not just a carousel of recipe steps — it is a complete UX shift designed for a kitchen environment where the phone is propped up 60cm away. The product decisions that matter: how precisely do background timers work on iOS (they are heavily restricted), whether voice control is push-to-talk or always-on (always-on causes false triggers from kitchen sounds), and whether the cook-along works fully offline for recipe apps with a travel or off-grid audience.

## Anatomy of the Feature

Six components. The cook mode UI and timer are generated correctly by AI tools; voice navigation and iOS background timer behavior are where first builds consistently break.

- **Cook mode UI** (ui): Flutter full-screen step card using flutter_screenutil for responsive typography that scales across phone sizes. PageView widget enables swipe-to-advance between steps. WakeLock.enable() called in initState keeps the screen on for the duration of the session.
- **Per-step timer** (ui): Flutter's Timer.periodic drives a countdown in seconds stored in Page State. A CustomPainter draws the ring progress indicator around the time display. flutter_local_notifications schedules a notification at the moment cooking starts so the alarm fires even if the app is sent to the background or killed by iOS.
- **Voice navigation** (service): Flutter speech_to_text package listens for wake words ('next step', 'go back', 'pause timer'). Processes on-device via ML Kit Speech Recognition — no internet required and no per-call cost. Confidence threshold set to 0.85 or higher to avoid false triggers from kitchen sounds.
- **Recipe data model** (data): Supabase tables: recipes (id, title, servings, image_url, prep_time_min, cook_time_min, user_id) and recipe_steps (id, recipe_id, step_number, instruction, timer_seconds, image_url). Serving scaler multiplies ingredient quantities by the ratio of new_servings to original_servings on the client — no server round-trip needed.
- **Ingredient checklist** (ui): Flutter CheckboxListTile widgets with local state managed by flutter_riverpod or Provider. Checked state persists in SharedPreferences for session continuity if the app is backgrounded mid-recipe. State resets automatically when cook mode is launched fresh.
- **Offline support** (backend): Hive or Isar local database caches the full recipe data (steps, ingredients, images) on the first successful load. Cook mode reads from the local cache, making the feature fully functional offline. Supabase sync runs on the next network connection to pick up any recipe edits.

## Data model

Three tables for recipes, steps, and ingredients with row-level security for draft/published state. Run in the Supabase SQL editor.

```sql
create table public.recipes (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade not null,
  title text not null,
  servings int not null default 4,
  image_url text,
  prep_time_min int not null default 0,
  cook_time_min int not null default 0,
  is_published bool not null default false,
  created_at timestamptz not null default now()
);

alter table public.recipes enable row level security;

create policy "Published recipes are viewable by everyone"
  on public.recipes for select
  using (is_published = true OR auth.uid() = user_id);

create policy "Owners can manage their recipes"
  on public.recipes for all
  using (auth.uid() = user_id);

create table public.recipe_steps (
  id uuid primary key default gen_random_uuid(),
  recipe_id uuid references public.recipes(id) on delete cascade not null,
  step_number int not null,
  instruction text not null,
  timer_seconds int,
  image_url text,
  unique (recipe_id, step_number)
);

alter table public.recipe_steps enable row level security;

create policy "Steps inherit recipe visibility"
  on public.recipe_steps for select
  using (
    exists (
      select 1 from public.recipes r
      where r.id = recipe_id
        and (r.is_published = true OR auth.uid() = r.user_id)
    )
  );

create policy "Owners can manage steps"
  on public.recipe_steps for all
  using (
    exists (
      select 1 from public.recipes r
      where r.id = recipe_id and auth.uid() = r.user_id
    )
  );

create table public.recipe_ingredients (
  id uuid primary key default gen_random_uuid(),
  recipe_id uuid references public.recipes(id) on delete cascade not null,
  name text not null,
  quantity numeric not null,
  unit text not null,
  order_index int not null
);

alter table public.recipe_ingredients enable row level security;

create policy "Ingredients inherit recipe visibility"
  on public.recipe_ingredients for select
  using (
    exists (
      select 1 from public.recipes r
      where r.id = recipe_id
        and (r.is_published = true OR auth.uid() = r.user_id)
    )
  );

create policy "Owners can manage ingredients"
  on public.recipe_ingredients for all
  using (
    exists (
      select 1 from public.recipes r
      where r.id = recipe_id and auth.uid() = r.user_id
    )
  );

create index recipe_steps_recipe_id_idx on public.recipe_steps (recipe_id, step_number);
create index recipe_ingredients_recipe_id_idx on public.recipe_ingredients (recipe_id, order_index);
```

Store ingredient quantity as a numeric value (e.g. 0.5 for half a cup). Formatting as fractions ('1/2 cup') is a display concern handled on the client — you cannot multiply a string by the serving ratio.

## Build paths

### Lovable — fit 2/10, 4–6 hours (web/PWA only)

Lovable builds React SPAs — a PWA cook-along is viable for basic step display and timers on Android Chrome, but degrades significantly compared to native: no WakeLock on iOS, no background notifications, no voice via ML Kit. Only choose this path if a mobile app is not an option.

1. Create a new Lovable project and connect Lovable Cloud for the recipe and steps tables
2. Paste the prompt below to build the step-by-step cooking mode as a PWA with web APIs
3. Test WakeLock on Android Chrome specifically — the Web WakeLock API is not supported on iOS Safari
4. Verify that the Web Speech API voice recognition works in the browser on Android — it requires a HTTPS URL
5. Publish and test on both Android Chrome and iOS Safari to document the PWA limitations for your users

Starter prompt:

```
Build a recipe cook-along feature as a web app. Recipe data model: Supabase tables recipes (id, title, servings, image_url, prep_time_min, cook_time_min, user_id) and recipe_steps (id, recipe_id, step_number, instruction, timer_seconds, image_url) and recipe_ingredients (id, recipe_id, name, quantity numeric, unit, order_index). Cook-along mode: full-screen view showing one step at a time with large text (24px minimum), step number out of total, previous/next buttons, and a progress bar. Serving size input at top that multiplies all ingredient quantities by new_servings/original_servings ratio. Ingredient checklist: checkboxes that persist in sessionStorage for the current session. Per-step timer: if step has timer_seconds, show countdown in MM:SS with a circular progress ring; use the Web Notifications API to fire a notification when timer expires (request permission first). WakeLock: call navigator.wakeLock.request('screen') when cook mode starts; release on exit. Voice control: use Web Speech API (webkitSpeechRecognition) to listen for 'next step', 'go back', 'pause timer' commands — only on button press (not continuous). Handle edge cases: step with no timer (hide timer UI), last step (show 'Cooking complete' card with confetti), zero servings input (prevent, show error), offline (read from cached Supabase data). Show a banner on iOS Safari warning that WakeLock and notifications are not supported.
```

Limitations:

- Web WakeLock API is not supported on iOS Safari — the screen will sleep after 30 seconds on iPhone
- Web Notifications require HTTPS and explicit permission; iOS Safari blocks background notifications in PWAs entirely
- Web Speech API (voice control) is not available on Firefox and has limited support on iOS Safari
- Background timers using setTimeout or setInterval are throttled by browsers when the tab is not visible

### Flutterflow — fit 5/10, 4–8 hours

The native mobile path and the right choice for this feature. FlutterFlow's Widget Tree maps directly to the step card layout; Flutter handles WakeLock, ML Kit speech, background timer notifications, and offline Hive storage natively.

1. Create a FlutterFlow project with Supabase integration; run the SQL schema from this page in the Supabase SQL editor
2. Build the cook mode page: add a PageView widget with one child per recipe step, bind step_number and instruction from the recipe_steps table query
3. Add a Page State variable for the countdown timer; use a Timer.periodic Custom Action to decrement it every second and fire a flutter_local_notifications notification when it reaches zero
4. Add a Custom Action for WakeLock using the wakelock_plus package — call enable in the page's init action and disable in the dispose action
5. Add voice control via a Custom Action using speech_to_text; bind recognized text to a page state variable and trigger next/previous step actions conditionally

Limitations:

- ML Kit speech recognition in a Custom Action requires the Pro plan ($70/mo) — it cannot be used on Standard
- Voice command accuracy drops in noisy kitchens; test with real background cooking noise before launching
- FlutterFlow auto-generated pubspec.yaml may include conflicting speech package versions on code export — reconcile dependency versions before the App Store build

### Custom — fit 4/10, 2–3 weeks

Full Flutter development gives maximum control over voice activation sensitivity, custom wake words, background timer reliability, and smart home device integration. Justified when the cook-along is the core differentiator of a recipe app.

1. Full Flutter development with flutter_riverpod for state management across the cook mode session
2. Custom wake word implementation using the PicoVoice Porcupine SDK for always-on activation without false triggers
3. Siri Shortcuts and Google Assistant integration so users can launch cook mode hands-free from the lock screen
4. Integration with Amazon Alexa and Google Home for smart home timer sync

Limitations:

- PicoVoice custom wake words require a license fee for commercial use
- Siri Shortcuts require the SiriKit entitlement and Apple Developer account configuration — 2–4 additional days of work

## Gotchas

- **WakeLock not releasing drains battery after cooking session ends** — WakeLock.enable() prevents the screen from sleeping but must be balanced by WakeLock.disable() when the user exits cook mode. If the widget is disposed without calling disable — because the user navigates back or the app is backgrounded — the screen lock stays active indefinitely, draining the battery for hours. Fix: Use the wakelock_plus package instead of the raw wakelock package — it provides automatic lifecycle handling via the WidgetsBindingObserver. Call WakelockPlus.enable() in initState and WakelockPlus.disable() in dispose() wrapped in a try/finally block to ensure it runs even if an exception occurs.
- **Background timer fires but app is already killed on iOS** — iOS aggressively suspends and kills apps that go to the background. Flutter's Timer.periodic is not reliable after 30 seconds in background. Users who switch to another app mid-timer get no notification when their pasta water finishes boiling — the most critical failure mode for a cooking app. Fix: Use flutter_local_notifications to schedule a notification at the exact timestamp when the timer should expire (DateTime.now().add(Duration(seconds: timerSeconds))). This schedules an OS-level notification that fires even if the app is killed. Do not rely on Timer.periodic alone for the user-facing alarm.
- **Voice commands trigger randomly from kitchen sounds** — The speech_to_text package keeps the microphone open continuously when voice mode is active. In a working kitchen with running water, sizzling, and background music, the recognizer picks up random audio and triggers 'next step' or 'pause timer' incorrectly — advancing through the recipe without any user intent. Fix: Increase the confidence threshold to 0.85 or higher to filter low-confidence recognitions. Add a 500ms debounce between accepted commands. Most importantly, make voice a secondary control — show a large push-to-talk button as the primary interaction and make voice activation opt-in. This dramatically reduces false triggers in real kitchens.
- **FlutterFlow Custom Action for ML Kit conflicts with existing speech plugin** — FlutterFlow auto-generates a pubspec.yaml that may include an older version of the speech_to_text package alongside the ML Kit plugin you added via Custom Action. Dart pub resolves to an incompatible version, and the Flutter build fails with a dependency conflict error only discovered at the App Store submission stage. Fix: After code export, open pubspec.yaml and manually reconcile the speech_to_text and google_mlkit_* dependency versions. Pin to the latest stable compatible versions and run flutter pub upgrade. Verify the build locally before uploading to TestFlight or Play Console.
- **Serving scaler shows 0.33333 recurring fractions** — Simple multiplication of ingredient quantities by a serving ratio produces floating-point fractions: 1/3 cup times 2 = 0.6666666. Displaying raw decimal fractions is confusing in a cooking context — 0.75 cups is far less useful than '3/4 cup'. Fix: Round liquid quantities to 1 decimal place. For small quantities, convert to the nearest useful cooking fraction: 0.25 = '1/4', 0.33 = '1/3', 0.5 = '1/2', 0.75 = '3/4'. Use a small fractions lookup function rather than displaying raw decimals.

## Best practices

- Schedule background timer notifications using flutter_local_notifications with a fixed fire time — never rely on Timer.periodic alone for the alarm, as iOS kills background processes
- Release WakeLock in the widget's dispose() method inside a try/finally block — an unreleased WakeLock drains the battery for hours after cooking ends
- Use push-to-talk as the primary voice control mechanism and always-on listening as opt-in secondary — kitchen noise produces too many false triggers for always-on to be reliable
- Store ingredient quantities as numeric values in the database and format to useful fractions (1/4, 1/3, 1/2) only at display time — strings cannot be multiplied by the serving ratio
- Cache recipe data and images to a local Hive or Isar database on first load so cook mode works fully offline — kitchens often have poor connectivity
- Use flutter_screenutil for all text and spacing so the cook mode UI is readable on both small (5-inch) and large (7-inch tablet) screens from counter distance
- Reset ingredient checklist state when cook mode starts fresh, but persist it in SharedPreferences across app-background events during a session so a phone call does not lose the user's progress
- Request microphone permission before the cook mode starts with a clear explanation — 'We use the microphone to respond to hands-free commands like Next step while you cook' increases permission grant rates

## Frequently asked questions

### Can I build a cooking app for iOS without a Mac?

You can build and design the app using FlutterFlow's web editor without a Mac. However, submitting to the iOS App Store requires a Mac with Xcode for the final build and upload — or a cloud build service like Codemagic that handles Xcode on a remote Mac. FlutterFlow's built-in iOS build exports a .ipa you can upload without a local Mac via Codemagic.

### How do I keep the phone screen on during cooking?

Use the wakelock_plus Flutter package: call WakelockPlus.enable() when cook mode starts and WakelockPlus.disable() in dispose(). This works on both Android and iOS. On iOS it maps to UIApplication.shared.isIdleTimerDisabled. Always release the lock when the user exits — an unreleased WakeLock drains the battery significantly.

### Does voice control work without internet?

Yes, when using ML Kit Speech Recognition via the speech_to_text Flutter package. ML Kit processes audio fully on-device using a downloaded language model. No audio is sent to a server, which means voice commands work offline and there are no per-call costs.

### How do I add multiple timers for the same recipe?

Each recipe step can have its own timer_seconds value in the recipe_steps table. Show a timer UI only for steps where timer_seconds is not null. To run multiple timers simultaneously (for example, 'simmer sauce' while 'boiling pasta'), manage multiple Timer.periodic instances in a map keyed by step ID, and schedule separate flutter_local_notifications for each one's expiry time.

### Can I sync recipes across devices?

Yes. Supabase handles cross-device sync automatically — when the user logs in on a new device, the app fetches their recipes from the database. The local Hive cache is device-specific and is refreshed from Supabase on each launch. Offline edits on one device are synced when the device reconnects.

### How do I scale a recipe for different serving sizes?

Store ingredient quantities as numeric values (e.g. 0.5 for half a cup). When the user changes the serving count, multiply each quantity by new_servings/original_servings. Round liquids to 1 decimal place and convert small quantities to useful cooking fractions: 0.25 = '1/4 cup', 0.333 = '1/3 cup', 0.5 = '1/2 cup'. Never store quantities as formatted strings like '1/2 cup' — you cannot do math on a string.

### What's the difference between a PWA recipe app and a native app?

A PWA (built with Lovable or V0) runs in the browser and has access to the Web WakeLock API on Android Chrome, but iOS Safari does not support WakeLock in PWAs and blocks background notifications. A native Flutter app (via FlutterFlow) has full access to WakeLock, flutter_local_notifications, ML Kit speech, and offline storage on both Android and iOS. For a cook-along feature where WakeLock and background timers are essential, native is the correct platform.

### Can I import recipes from other apps?

Recipe import from external apps requires parsing each app's export format, which varies widely. The most portable format is JSON-LD recipe schema markup that many recipe sites embed in their HTML. A Supabase Edge Function can fetch a URL, extract the JSON-LD, and map it to your recipe_steps table. Full import from proprietary apps like Yummly or Paprika requires format-specific parsing.

---

Source: https://www.rapidevelopers.com/app-features/interactive-recipe-cook-along
© RapidDev — https://www.rapidevelopers.com/app-features/interactive-recipe-cook-along
