Flutter Classified Ads Engine
Copy the prompt below and use it with any AI
Prompt
You are a senior Flutter architect. Build a production-grade
config-driven Classified Ads Engine mobile app in Flutter.
The entire app transforms into a different product by changing
one constant in main.dart. Same codebase becomes a Marketplace,
Job Board, Rental app, or Second-hand store.
TECH STACK:
- Flutter
- Firebase Auth (Google Sign-In only)
- Firestore
- Cloudinary (image uploads)
- flutter_bloc (state management)
- get_it (dependency injection)
- go_router (navigation)
- dartz (Either for error handling)
- Clean Architecture, feature-first folder structure
STRICT RULES:
- Firebase only in data/datasources layer. Never in BLoC or UI.
- Cloudinary behind an abstract interface
- Every feature has domain/ data/ presentation/ layers
- Use Either<Failure, T> for all repository returns
- No hardcoded strings — everything flows from AppConfig
THE CONFIG SYSTEM (most important part):
class AppConfig {
final String appName;
final String appTagline;
final AppDomain domain;
final bool enableChat;
final bool enablePrice;
final bool enableLocation;
final bool enableCategories;
final bool enableFavorites;
final bool enableRoleSwitch;
final bool enableImageUpload;
final bool enableContactSeller;
final String listingLabel;
final String listingLabelPlural;
final String sellerLabel;
final String buyerLabel;
final String priceLabel;
final String locationLabel;
final String currencySymbol;
final String currencyCode;
final CategoryPreset? categoryPreset;
}
Include 4 ready-to-use preset configs:
- marketplaceConfig (Marketplace, buy/sell anything)
- jobBoardConfig (Job board, employer/job seeker)
- rentalConfig (Rental listings, landlord/tenant)
- secondHandConfig (Second-hand goods, no role switch)
Switching between them = one line change in main.dart.
FOLDER STRUCTURE:
lib/
├── config/
│ ├── app_config.dart
│ └── category_presets.dart
├── core/
│ ├── di/injection_container.dart
│ ├── error/failures.dart + exceptions.dart
│ ├── usecases/usecase.dart
│ ├── network/network_info.dart
│ └── utils/constants.dart + app_utils.dart
├── features/
│ ├── auth/
│ ├── listings/
│ ├── categories/
│ ├── favorites/
│ ├── profile/
│ └── role/
├── shared/
│ ├── navigation/app_router.dart
│ ├── theme/app_theme.dart
│ └── widgets/
└── main.dart
BLOCS TO BUILD:
- AuthBloc (Google sign-in, auth state stream)
- ListingBloc (browse, search, filter, paginate, CRUD)
- FavoritesBloc (real-time Firestore stream)
- CategoryBloc (load categories, track selection)
- RoleBloc (buyer/seller toggle, persist to SharedPreferences)
- ProfileBloc (load and update user profile)
FIRESTORE SCHEMA:
users/{userId}
- email, displayName, photoUrl, bio, phone, city,
listingCount, joinedAt
listings/{listingId}
- title, description, price, images, categoryId,
userId, userDisplayName, userPhotoUrl, location,
status (active/sold/draft), metadata, createdAt
favorites/{userId}_{listingId}
- userId, listingId, savedAt
categories/{categoryId}
- name, icon (emoji), color (hex), listingCount
MVP FEATURES:
1. Google Sign-In with persistent auth state
2. Browse listings — masonry grid + list toggle + infinite scroll
3. Search and filter — category chips, price range, city, sort
4. Listing detail — image gallery with fullscreen viewer
5. Create listing — image picker, category selector, location
6. Edit and delete listing (owner only)
7. Favorites — real-time stream, heart button on every card
8. Role switching — buyer vs seller, persisted to SharedPreferences
9. Profile page — role-aware (sellers see their listings and a
Post button, buyers see an upsell to switch to seller mode)
10. Edit profile — name, bio, phone, city
GOROUTER ARCHITECTURE (critical — get this right):
GoRouter ShellRoute creates a new widget subtree that is
completely disconnected from main.dart's MultiBlocProvider.
Global blocs in main.dart (available everywhere):
- AuthBloc, RoleBloc, FavoritesBloc
Shell blocs in MainScaffold (available to shell routes only):
- ListingBloc, CategoryBloc, ProfileBloc
Self-contained blocs (each pushed page provides its own):
- CreateListingPage: own ListingBloc + CategoryBloc
- EditListingPage: own ListingBloc + CategoryBloc
- ListingDetailPage: own ListingBloc
- EditProfilePage: own ProfileBloc
- ProfilePage: own ProfileBloc + own ListingBloc
Any page reached via context.push() must provide its own
BlocProviders. Any page reached via context.go() inside the
shell can use the shell's blocs.
FIRESTORE QUERY RULE (critical for MVP):
Do not combine where() and orderBy() on different fields.
This requires composite indexes that don't exist yet.
Instead: query with single field only, sort client-side.
Example: .where('userId', isEqualTo: id) then sort in Dart.
CATEGORY PRESETS:
Marketplace: Electronics, Vehicles, Furniture, Clothing,
Sports, Books, Home & Garden, Other
Job Board: Technology, Design, Marketing, Finance,
Healthcare, Education, Sales, Remote
Rental: Apartment, House, Studio, Room, Commercial, Land
Second-hand: Electronics, Clothing, Toys, Books,
Sports, Furniture, Other
UI REQUIREMENTS:
- Material 3 design
- Custom theme with AppColors and AppTextStyles
- Shimmer loading skeletons
- Empty states with icons and action buttons
- Error states with retry
- Loading overlay for async operations
- Masonry grid (flutter_staggered_grid_view)
- Cached network images (cached_network_image)
- Photo gallery with fullscreen viewer (photo_view)
CLOUDINARY:
- Upload via unsigned upload preset
- Abstract behind CloudinaryDataSource interface
- Auto-optimize on delivery using transformation URLs
- Support multiple images per listing (max 5)
ALSO INCLUDE:
- firestore.rules with proper security rules
- firestore.indexes.json for composite indexes
- Complete README with domain transformation guide,
feature flag reference, and architecture decisions