The Problem
Furniture shopping online is broken. Customers can't visualize how a piece will look in their space, vendors struggle to reach buyers without building their own apps, and platform owners need a way to orchestrate it all. Mafroosh set out to solve all three problems in a single platform.
Architecture Overview
Mafroosh is a three-application system:
• Customer App — a Flutter mobile app where users browse, visualize furniture in AR, and purchase from multiple vendors
• Vendor Panel — a dashboard where sellers manage inventory, fulfill orders, and track analytics
• Admin Panel — a control center for platform-wide operations: vendor approvals, product moderation, and order oversight
All three share a single Firebase backend (Firestore, Auth, Cloud Functions, Storage), which kept infrastructure costs low and data consistent across surfaces.
Flutter gave us a single codebase targeting iOS and Android with native performance — critical for the AR experience. Firebase provided real-time sync out of the box, which powered the chat system and live order tracking without building a custom WebSocket layer. Firestore's document model mapped cleanly to our multi-vendor data: each vendor is a document, products are subcollections, and Firestore security rules enforce vendor-level data isolation.
State Management with GetX
We chose GetX over Bloc or Riverpod for a practical reason: it collapses routing, dependency injection, and reactive state into a single package. With 50+ routes and 38+ controllers across the app, reducing boilerplate mattered. Each feature module follows a consistent pattern:
feature/
├── screens/ # UI layer
├── controllers/ # Reactive state (GetX .obs variables)
├── models/ # Serializable data classes
Controllers are lazily injected via Get.lazyPut(), so memory footprint stays proportional to what the user is actually using.
Feature-First Modular Structure
Rather than grouping by layer (all controllers in one folder, all screens in another), we grouped by feature: authentication/, shop/, personalization/, chat/. This made it possible for work on the checkout flow to happen in isolation from the chat system. Each feature owns its full vertical slice — screens, controllers, models, and screen-specific widgets.
Repository Pattern for Data Access
A layer of 22 feature-specific repositories sits between controllers and Firebase. This wasn't premature abstraction — it let us swap Firestore queries without touching UI code, batch reads efficiently, and keep controllers focused on state rather than serialization logic.
AR Furniture Visualization
The standout feature is placing 3D furniture models in a user's real room using their camera. We integrated Unity's AR Foundation into the Flutter app via flutter_unity_widget. The main challenge was bridging two runtimes: Flutter owns the UI and navigation, while Unity owns the 3D scene and camera feed.
Communication happens over a message channel — Flutter sends product metadata and model URLs to Unity, Unity sends back placement confirmations and screenshots. We used wakelock_plus to prevent the screen from sleeping during AR sessions, since users need sustained camera access while positioning furniture.
Real-Time Chat with Audio Messages
The chat system supports text and audio messages between customers and vendors. Audio was the tricky part: we used audio_waveforms for recording with a visual waveform preview, and audioplayers for playback. Messages sync in real-time through Firestore listeners, with flutter_chat_ui handling the conversation interface.
The key design decision was storing audio files in Firebase Storage and only keeping the download URL in the Firestore message document. This kept document sizes small and reads fast, while letting us serve audio through Firebase's CDN.
Multi-Language Support at Scale
Mafroosh supports 7 languages (English, French, German, Portuguese, Russian, Spanish, Vietnamese). We used GetX's translation system, where each language is a Dart map of key-value pairs accessed via .tr. The main challenge wasn't the translation mechanism — it was ensuring RTL-aware layouts and handling text that expands significantly between languages (German strings are often 30% longer than English).
Payment Integration
We integrated both Stripe and PayPal to maximize regional coverage. Stripe handles card payments through flutter_stripe, while PayPal covers markets where card penetration is lower. Payment processing runs through Firebase Cloud Functions to keep API keys server-side and validate transactions before writing order records to Firestore.
Performance Considerations
• Shimmer loading screens replace spinners throughout the app, giving users spatial context for incoming content rather than a blank screen
• Cached network images with cached_network_image prevent redundant downloads of product photos across sessions
• Lazy controller injection means unused features don't consume memory
• Firestore pagination on product listings and order history prevents loading entire collections
What I'd Do Differently
• Offline support: Firestore has offline persistence, but we didn't fully design around it. A proper offline-first architecture with optimistic UI updates would improve the experience on spotty connections.
• Testing: The codebase would benefit from widget tests on critical flows (checkout, auth) and integration tests for the Firebase repository layer.
• Modularization: As the app grew past 50 routes, build times increased. Breaking features into separate Dart packages would enable incremental compilation.
Stack Summary
Mobile Framework: Flutter (Dart 3.8+)
State Management: GetX
Backend: Firebase (Firestore, Auth, Functions, Storage)
AR Engine: Unity AR Foundation via flutter_unity_widget
Payments: Stripe, PayPal
Real-Time Chat: Firestore listeners + flutter_chat_ui
Localization: GetX translations (7 languages)
CI/CD: Firebase Hosting, Crashlytics for error tracking
