<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vlad Vladescu</title>
    <description>The latest articles on DEV Community by Vlad Vladescu (@vladvladescu).</description>
    <link>https://dev.to/vladvladescu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3981089%2Fbedb0b53-fdeb-4540-a031-0dbdbb658573.jpeg</url>
      <title>DEV Community: Vlad Vladescu</title>
      <link>https://dev.to/vladvladescu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vladvladescu"/>
    <language>en</language>
    <item>
      <title>Shipping an LLM-powered "lie detector" in a Flutter dating app</title>
      <dc:creator>Vlad Vladescu</dc:creator>
      <pubDate>Fri, 12 Jun 2026 10:57:34 +0000</pubDate>
      <link>https://dev.to/vladvladescu/shipping-an-llm-powered-lie-detector-in-a-flutter-dating-app-597f</link>
      <guid>https://dev.to/vladvladescu/shipping-an-llm-powered-lie-detector-in-a-flutter-dating-app-597f</guid>
      <description>&lt;p&gt;How I added a production AI feature to Naked — a psychology-based dating app — using Flutter, Firebase Cloud Functions, and the Claude API.&lt;/p&gt;

&lt;p&gt;The product problem&lt;br&gt;
Naked matches people on psychological compatibility: users complete assessments (attachment style, communication style, Big Five…) and get matched on the results. But self-reported questionnaires have a known weakness — people answer how they want to appear, not how they are. A rule-based scoring engine can compute "your attachment style is anxious"; it cannot notice that question 4 and question 11 contradict each other.&lt;/p&gt;

&lt;p&gt;That pattern-level analysis is exactly what LLMs are good at. The feature: after completing any assessment, the user can request an AI Insight that analyzes the raw answer pattern — strengths, growth areas, a dating tip, and a consistency analysis that flags contradictions, fence-sitting (all-neutral answers), and social-desirability bias. Once the user's match completes the same test, the insight upgrades with a couple-dynamics comparison.&lt;/p&gt;

&lt;p&gt;Architecture&lt;br&gt;
Flutter app (quiz UI, BLoC + Clean Architecture)&lt;br&gt;
   │  httpsCallable — Firebase Auth token attached automatically&lt;br&gt;
   ▼&lt;br&gt;
Firebase Cloud Function (Node.js, the only holder of the API key)&lt;br&gt;
   │  auth gate → payload validation → cache check → Claude call&lt;br&gt;
   ▼&lt;br&gt;
Claude API (claude-opus-4-8, schema-enforced structured output)&lt;br&gt;
   │&lt;br&gt;
   ▼&lt;br&gt;
Firestore (insight persisted per user/test + token-usage audit trail)&lt;br&gt;
Five decisions I'd defend in any review&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The API key never ships in the binary. Anything inside an APK/IPA can be extracted in minutes. The app only talks to a Firebase callable function that requires an authenticated user; the Anthropic key lives in Firebase Secret Manager and exists only inside the function's runtime.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Structured outputs instead of "please reply in JSON". The Claude call uses a JSON schema the API enforces — including enums for the consistency verdict and confidence. No regex extraction, no retry-on-malformed-JSON, and the Flutter side parses straight into typed entities.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cost discipline from day one. On a paid path, never trust the client: the function caps answer counts and string lengths so a tampered client can't inflate token spend. Responses are cached in Firestore keyed by a SHA-256 hash of the inputs — an identical request is served free. Every generation stores its token usage, so spend is auditable per user from the console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cache invalidation by design, not by flag. The cache hash includes the partner's result. Same answers + no partner and same answers + partner data are different hashes — so when the match completes the test, a refresh regenerates the insight automatically. The hash is the staleness check.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A privacy boundary the model must respect. The consistency analysis is private: Firestore rules make insights readable only by their owner, and the system prompt explicitly forbids leaking candour observations into the shared partner-dynamics text. Only the partner's computed result is ever sent to the model — never their raw answers.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Process&lt;br&gt;
TDD: the BLoC tests were written before the implementation — 8 tests covering success, failure, double-tap guarding, stored-insight loading, and partner-aware refresh, with the repository mocked.&lt;br&gt;
Inputs persisted, not just outputs: every quiz submit snapshots the raw answers, so insights can be regenerated later (e.g. when the partner finishes the test) without retaking the quiz.&lt;br&gt;
Production debugging: the first deploy failed transiently mid-creation, leaving the callable without its public-invoker IAM binding (403 at the HTTP layer before any code runs). Diagnosed with a curl smoke test, fixed with one gcloud IAM command — a reminder that "deployed" isn't "verified".&lt;br&gt;
Stack&lt;br&gt;
Flutter · BLoC + Clean Architecture · Firebase (Auth, Firestore, Cloud Functions, Secret Manager) · Claude API (claude-opus-4-8, structured outputs) · Node.js · mocktail/bloc_test&lt;/p&gt;

&lt;p&gt;Vlad Vladescu — Senior Mobile Engineer (Flutter &amp;amp; Android), ex-British Telecom (EE app, 12M+ users). Open to senior Flutter / mobile-AI roles, remote EU. linkedin.com/in/vlad-vladescu-180733121&lt;/p&gt;

&lt;h1&gt;
  
  
  flutter, #ai, #firebase, #mobile
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>flutter</category>
      <category>llm</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
