Live

Outfii

AI virtual try-on powered by Reve Remix — upload one photo, then swipe through garments or compose multi-piece outfits and see realistic results in seconds.

Demonstrates Content-addressed caching and atomic credit accounting — SHA-256 keyed result reuse, transactional Firestore deductions, multi-image prompt engineering, A/B-tested paywall variants.
100Pro credits / mo
500MBLRU cache
2Modes (swipe + builder)
SHA-256Cache key

Outfii has two product modes that share one engine. Swipe is a Tinder-style stack of garments — tap to try the next one on your photo. Builder lets you compose up to 5 garments into a complete outfit and generate them as one composition. Every result caches locally, so re-viewing past try-ons is instant and free.

The whole pipeline routes through Firebase Cloud Functions to a Reve Remix endpoint, with credits debited atomically and a CDN-aware cache invalidated on profile-photo changes.

AppFlutter 3.10 · iOS 13+ / Android API 21+
StateRiverpod 3.3 with code-gen (@riverpod)
RoutingGoRouter 17 with indexed-stack bottom nav
BackendFirebase — Firestore, Cloud Storage, Cloud Functions v1 (TypeScript)
AIReve Remix API (api.reve.com/v1/image/remix)
PaymentsRevenueCat 9.16 — Pro subscription + bonus credit packs
Image processingimage_picker, image_cropper, cached_network_image
Local cacheHive index + filesystem image bytes
AnalyticsFirebase Analytics + Crashlytics + TikTok Events SDK + ATT
Hostingoutfii.app

Feature-first Flutter layout with a TypeScript Cloud Functions backend. Generation cache is content-addressed by SHA-256 of profilePhotoUrl|garmentIds|mode; cache invalidates atomically on profile-photo change so a new selfie never pulls stale outfits.

lib/
├── core/
│   ├── providers/      Auth, subscriptions, generation state (Riverpod)
│   ├── models/         Freezed: User, Garment, Generation, Subscription
│   ├── services/       FirestoreService · StorageService · FashnService
│   │                   RevenueCatService · GenerationCacheService
│   │                   GenerationQueueService
│   └── widgets/        TryOnPill · BottomBlurFade · FilterBottomSheet
├── features/
│   ├── home/           Swipe Mode — photo carousel + garment stack
│   ├── builder_mode/   Outfit Builder + Result screen
│   ├── wardrobe/       User uploads + explore catalog
│   ├── gallery/        Saved results, share intent
│   ├── paywall/        4 RevenueCat-backed variants
│   ├── auth/           Google + Apple Sign-In
│   └── onboarding/

functions/src/
├── tryon.ts            generateTryOn() · generateOutfit() with credit deduction
├── reve.ts             generateReveRemix() wrapper
├── credits.ts          purchaseCredits() · initializeProCredits()
└── utils.ts            callReveRemix() · buildPrompt() · image encoding
01

Swipe Mode

Cardstack UI; tap any garment to try it on your current profile photo. Rapid A/B testing of looks without commitment.

02

Builder Mode

Multi-select up to 5 garments and one prompt. Reve generates a complete composed outfit with category-aware instructions (one-pieces vs. tops + bottoms).

03

Dual-source garments

User uploads (subcollection) + curated explore catalog. Filter by category, favorite, save.

04

500 MB local cache

Content-addressed SHA-256 keys, LRU eviction, survives app restart. Re-viewing past try-ons is instant and free.

05

Credit system

100 monthly Pro credits + optional 20/50/100 bonus packs. Atomic Firestore transaction prevents going negative.

06

Remote-config A/B

Onboarding try-on toggle, paywall variant, TikTok events kill switch — all gated through Remote Config.

  1. 01

    Profile photo

    image_picker → cropped to consistent aspect → uploaded to users/{uid}/profile/{photoId}.jpg in Cloud Storage.

  2. 02

    Pick garment(s)

    Swipe stack or Builder selection; app resolves originalImageUrl from GarmentModel.

  3. 03

    Callable function

    FashnService.generateTryOn() hits Cloud Callable with 300 s timeout. Function validates auth, category, URLs, tier and credits.

  4. 04

    Reve Remix call

    Both images downloaded as base64 and passed with an indexed prompt: "person from <img>1</img> wearing top from <img>2</img>".

  5. 05

    Credit deduction

    1 credit per garment, monthly pool first → bonus pool. Transaction in deductCredit() ensures atomicity.

  6. 06

    Cache & display

    Result PNG cached locally by SHA-256 key, stored in Firestore generations subcollection, rendered in HomeScreen / gallery.

Atomic credit deduction

Firestore transaction reads tier + monthly + bonus pools, validates positivity, writes the new state. Monthly resets if the reset date has passed.

Indexed multi-image prompts

Reve API takes <img>1</img>, <img>2</img> markers; buildPrompt() branches on category — one-pieces instruct "copy everything exactly as shown" to avoid bleed.

500 MB content-addressed cache

SHA-256 of profile + garments + mode is the key. Re-viewing past results is free. Cache invalidates atomically when the profile photo changes.

Generation queue with reasons

In-memory queue tracks task status and error reasons (paywall, credits, content-violation, generic) for actionable retry UX rather than generic toasts.

TikTok ATT workaround

Conditional init in main.dart + iOS native method-channel enableTracking to bypass the Flutter SDK's ATT gating, which blocks ~80% of users.

Crashlytics noise reduction

Transient errors (network, image decode, auth races) demoted from FATAL to non-fatal via error pattern matching to keep the dashboard signal-rich.

The decisions behind a fast app on top of a slow API.

Client-side cache over server-side

SHA-256-keyed local storage means re-viewing past try-ons is instant and free. Cost: clients must enforce LRU eviction and atomic invalidation correctly — a server-side cache would centralize that complexity.

Reve API over self-hosted Stable Diffusion

Hosted API ships in days, no GPU ops. Cost: vendor lock-in for image quality, and if Reve raises prices or drops a model, the product is exposed.

Credit deduction at preparation, not publish

Charging at task start prevents free generations on retry. Cost: refund logic on hard failures has to walk back monthly + bonus pools cleanly — simpler at-publish charging avoids it but lets users freeload through cancellation.

4 paywall variants live, not 1

Remote Config gates four paywall flows for A/B testing — the data drives revenue optimization. Cost: four code paths to keep working as the app evolves; cheaper paywalls would mean less measurement.

Free
  • 0 generations
  • Browse garments + explore catalog
Pro
  • 100 monthly credits (resets monthly)
  • Bonus packs: 20 / 50 / 100
  • 4 paywall variants A/B-tested via Remote Config

Stage: Live. Hard paywall gated through Remote Config (hard_paywall_enabled); RevenueCat orchestrates IAP across iOS + Android.

Crossover to enterprise work

Atomic credit accounting is the same problem as audit-grade financial state.

An atomic Firestore transaction that reads tier + monthly + bonus pools, validates positivity, and writes a new state is structurally the same primitive enterprise systems use for audit-traceable financial logic — tested in invoice systems, license metering and resource quota management. Content-addressed cache invalidation on profile-photo change is the same pattern cache-coherent distributed systems need to avoid stale reads. Cloud Function callable signatures with strict auth + resource validation map directly to secured Mendix REST endpoints in regulated environments.