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.
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.
@riverpod)api.reve.com/v1/image/remix)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
Swipe Mode
Cardstack UI; tap any garment to try it on your current profile photo. Rapid A/B testing of looks without commitment.
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).
Dual-source garments
User uploads (subcollection) + curated explore catalog. Filter by category, favorite, save.
500 MB local cache
Content-addressed SHA-256 keys, LRU eviction, survives app restart. Re-viewing past try-ons is instant and free.
Credit system
100 monthly Pro credits + optional 20/50/100 bonus packs. Atomic Firestore transaction prevents going negative.
Remote-config A/B
Onboarding try-on toggle, paywall variant, TikTok events kill switch — all gated through Remote Config.
- 01
Profile photo
image_picker → cropped to consistent aspect → uploaded to
users/{uid}/profile/{photoId}.jpgin Cloud Storage. - 02
Pick garment(s)
Swipe stack or Builder selection; app resolves
originalImageUrlfrom GarmentModel. - 03
Callable function
FashnService.generateTryOn()hits Cloud Callable with 300 s timeout. Function validates auth, category, URLs, tier and credits. - 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>". - 05
Credit deduction
1 credit per garment, monthly pool first → bonus pool. Transaction in
deductCredit()ensures atomicity. - 06
Cache & display
Result PNG cached locally by SHA-256 key, stored in Firestore
generationssubcollection, 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.
- 0 generations
- Browse garments + explore catalog
- 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.
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.