DEV Community

Cover image for The 4 AI Patterns That Actually Ship in Mobile
Sophie F A
Sophie F A

Posted on

The 4 AI Patterns That Actually Ship in Mobile

Most AI features in mobile apps die before ship. The ones that make it reduce to four patterns. Here they are with real numbers.

The three constraints that kill 80% of AI features

Before the patterns, the filter. An AI feature ships in a mobile app if and only if it clears these:

  1. P95 latency under 2s (or streamed with TTFT < 500ms)
  2. Cost per active user under $0.10/mo (unless paid tier is live day one)
  3. A degradation story — offline, wrong answer, model down

Every pattern below clears all three. Every pattern that doesn't is why your last AI feature never left staging.


Pattern 1 — Transcribe → Structure

Shape: voice in → structured artifact out (summary, action items, journal entry).

// 1. capture audio (expo-av / expo-audio)
// 2. stream to Whisper / Deepgram / AssemblyAI
// 3. LLM call with a JSON schema
// 4. render + persist
Enter fullscreen mode Exit fullscreen mode

Numbers:

Stage P50 P95 Cost / 5-min recording
Upload 400ms 1.2s free
Whisper transcription 3s 7s $0.03
gpt-4o-mini structuring 800ms 2.1s $0.002

The UX trick: stream the transcript in during upload. The user never waits on the wall clock. Perceived latency stays under 1s because the UI is animating continuously.

Reference implementation: the AI Voice Notes template on Applighter ships this end-to-end — 16+ screens, waveform visualizer, pluggable transcription backend.


Pattern 2 — Chat over your data (RAG)

Shape: documents + question → grounded answer.

// ingest (once per doc)
const chunks = chunkPdf(file, { size: 800, overlap: 100 });
const embeddings = await openai.embeddings.create({
  model: 'text-embedding-3-small',
  input: chunks.map(c => c.text),
});
await supabase.from('doc_chunks').insert(
  chunks.map((c, i) => ({ ...c, embedding: embeddings.data[i].embedding }))
);

// query (every message)
const q = await embed(userQuestion);
const { data: matches } = await supabase.rpc('match_chunks', {
  query_embedding: q,
  match_count: 6,
});
const answer = await streamChat({
  messages: [
    { role: 'system', content: `Answer using only:\n${format(matches)}` },
    { role: 'user', content: userQuestion },
  ],
});
Enter fullscreen mode Exit fullscreen mode

Cost profile — 1,000 users × 20 msg/day:

  • Embeddings: ~$4/mo
  • LLM (gpt-4o-mini): ~$18/mo
  • pgvector on Supabase free tier: $0
  • Total: ~$22/mo → $0.022/user

Charge $4.99. Print money.

Supabase's pgvector is the low-ops choice — same Postgres you already use for auth. Pinecone/Weaviate/Chroma only make sense above ~100k docs.

Reference: Supabase vector columns docs.


Pattern 3 — Streaming generation

Shape: prompt → streamed text/JSON.

The transport is the whole game. Same model, same prompt, but token-by-token output shows meaningful content in <300ms. Non-streamed on cellular feels broken; streamed feels instant.

React Native gotcha: default fetch doesn't reliably stream on Android. Use expo/fetch:

import { fetch } from 'expo/fetch';

const res = await fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ messages }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  setStreamedText(prev => prev + parseSSE(decoder.decode(value)));
}
Enter fullscreen mode Exit fullscreen mode

Three things that break in prod:

  1. Backgrounding. iOS suspends the stream. Buffer server-side, reconnect on resume.
  2. FlatList reflow. Appending chars re-measures rows every keystroke. Memoize, or use a fixed-height container.
  3. Interruption. Users tap to type mid-stream. Need cancel-and-restart, not a modal.

Pattern 4 — Multimodal vision extraction

Shape: photo → structured JSON.

const image = await ImagePicker.launchCameraAsync({ base64: true });
const res = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [
    { role: 'system', content: 'Extract data as JSON matching this schema...' },
    { role: 'user', content: [
      { type: 'text', text: 'Extract line items' },
      { type: 'image_url', image_url: { url: `data:image/jpeg;base64,${image.base64}` } },
    ]},
  ],
  response_format: { type: 'json_schema', json_schema: RECEIPT_SCHEMA },
});
Enter fullscreen mode Exit fullscreen mode

Gotchas:

  • Resize first. iPhone photos are 3–5MB base64. Resize to 1024×1024 max, save 80% of tokens, no accuracy loss.
  • The model will guess. Always show the extraction with editable fields before saving.
  • P50 is ~3s. Show a skeleton, don't block the camera UI.

Side by side

Pattern Model P50 latency Cost/event Ships in
Transcribe → structure Whisper + gpt-4o-mini 4s $0.03 1 weekend
RAG gpt-4o-mini + pgvector 1.5s streamed $0.001/msg 3–5 days
Streaming generation gpt-4o-mini 300ms TTFT $0.0005/reply 2 days
Vision extraction gpt-4o 3s $0.01 2–3 days

The pattern that never ships: agents

Autonomous agents fail silently and expensively. A stuck agent burns $2 of API before anyone notices. A hallucinating agent sends the wrong Slack. On desktop, fine — you're watching a terminal. On mobile — backgrounded, killed, lost network mid-run — it's a support ticket factory.

The four patterns above ship because they're bounded: user sees input, user sees output, loop closes in seconds.

Agents will get there. Not in 2026.


Picking your pattern

  • Users speak more than they type → Pattern 1
  • Users have their own data → Pattern 2
  • Users compose text → Pattern 3
  • Users capture from the physical world → Pattern 4

Ship one. Compose later. Never all four at once — that's the roadmap slide of a startup that no longer exists.

Canonical version with more code + template links

Top comments (0)