Flutter: Project Design — ShopApp Architecture Planning
Draw the blueprint before building — architecture design determines where the "load-bearing walls" of your code are.
📋 Prerequisites: You should have mastered the following first
- Lesson 1: Flutter Introduction and Environment Setup
- Lesson 2: Dart Language Crash Course
- Lesson 3: Widget Fundamentals
- Lesson 4: Layout System
- Lesson 5: StatefulWidget and Interaction
- Lesson 6: Material Design and Common Components
- Lesson 7: Phase 1 Practice — ShopApp Starter
- Lesson 8: Navigation and Routing
- Lesson 9: Form and Input
- Lesson 10: List and Scrolling
- Lesson 11: Networking and REST API
- Lesson 12: State Management — Riverpod
- Lesson 13: Local Storage
- Lesson 14: Phase 2 Practice — ShopApp Core
- Lesson 15: Animation System
- Lesson 16: Theming and Styling
- Lesson 17: Platform Channels and Native Interop
- Lesson 18: Firebase Integration
- Lesson 19: Internationalization and Localization
- Lesson 20: Custom Painting and CustomPainter
- Lesson 21: Testing — Unit/Widget/Integration
- Lesson 22: Performance Optimization
- Lesson 23: Web and Desktop Multi-Platform Deployment
- Lesson 24: CI/CD Automation
- Lesson 25: Multi-Platform Publishing and Store Review
1. What You Will Learn
- Requirements analysis: user stories (Alice browsing and ordering / Bob merchant management / Charlie cross-border payment)
- Technology selection: Flutter 3.x + Riverpod + GoRouter + Dio + Firebase + Hive
- Architecture design: Clean Architecture layering (Domain / Data / Presentation)
- Data modeling: Product / Cart / Order / User entities and Firestore Schema
- UI/UX design: wireframes + component library planning + design token system
2. A Story of a Project Without Architecture
(1) The Pain: Vague Requirements + Chaotic Architecture
When Bob received the ShopApp project, the only requirement was: "Build a cross-border e-commerce app." He started coding immediately, and 3 months later discovered: user stories were unclear (merchant management features were missing), data models kept changing (Order was rewritten 3 times), and architecture was heavily coupled (changing payment affected the product list). The project was delayed by 2 months.
(2) Design First, Code Later
The project design phase clarifies requirements, architecture, data models, and UI planning upfront — coding then becomes "building from the blueprint."
(3) The Result: Coding Time Halved + Change Cost Reduced by 80%
After spending 2 weeks on design, Bob completed coding in 4 weeks (instead of the previous 3+2 months), and requirement changes only required modifying the corresponding layer without affecting other modules.
3. Requirements Analysis
(1) User Stories
graph TD
subgraph User Stories
ALICE[Alice: Browse → Order → Pay $299.99]
BOB[Bob: Manage Products → View Orders]
CHARLIE[Charlie: Cross-border → JPY → International Payment]
end
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; please use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
| Role | User Story | Priority |
|---|---|---|
| Alice (Consumer) | Browse million-level product list | P0 |
| Alice | Search/filter/browse by category | P0 |
| Alice | View product details + reviews | P0 |
| Alice | Add to cart + manage quantities | P0 |
| Alice | USD/CNY/JPY multi-currency settlement | P1 |
| Alice | View order status + logistics tracking | P1 |
| Bob (Merchant) | Manage product listing/delisting + inventory | P1 |
| Bob | View sales data reports | P2 |
| Charlie (Cross-border User) | Cross-border shopping + international payment | P2 |
| Charlie | Multi-language switching (EN/ZH/JA) | P1 |
4. Technology Selection
(1) Technology Stack Decisions
| Domain | Selection | Rationale |
|---|---|---|
| UI Framework | Flutter 3.x (Material 3) | Cross-platform + custom paint engine |
| State Management | Riverpod 2.x (@riverpod) | Type-safe + code generation |
| Routing | GoRouter | Declarative + Web URL support |
| Networking | Dio + Interceptors | Interceptors + Token refresh |
| Backend | Firebase (Auth+FS+Storage) | Zero ops + real-time sync |
| Cache | Hive | Lightweight NoSQL + offline |
| Encrypted Storage | flutter_secure_storage | Secure token storage |
| Serialization | json_serializable + freezed | Type-safe + immutable |
| Internationalization | flutter_localizations + intl | ARB workflow |
| Testing | flutter_test + mocktail | Unit/Widget/Integration |
| CI/CD | GitHub Actions | Free tier + multi-matrix |
(2) Riverpod vs BLoC Comparison
| Dimension | Riverpod | BLoC |
|---|---|---|
| Learning curve | Medium | High |
| Boilerplate | Less (@riverpod) | More (Event/State/Bloc) |
| Type safety | Strong | Strong |
| Testing | Simple | Simple |
| Code generation | ✅ | ❌ |
| Best for | Small-to-medium projects | Large team projects |
5. Clean Architecture Layering
graph TD
subgraph Presentation
PAGE[Pages/Widgets]
NOTI[Notifiers/Providers]
end
subgraph Domain
ENT[Entities]
REPO_I[Repository Interfaces]
USECASE[Use Cases]
end
subgraph Data
REPO_IMPL[Repository Impl]
DS[Data Sources]
DTO2[DTOs / Models]
end
PAGE --> NOTI
NOTI --> USECASE
USECASE --> REPO_I
REPO_I -.-> REPO_IMPL
REPO_IMPL --> DS
DS --> DTO2
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; please use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
(1) Layer Responsibilities
| Layer | Directory | Responsibility | Dependency Direction |
|---|---|---|---|
| Presentation | screens/, widgets/ |
UI + user interaction | → Application |
| Application | notifiers/, usecases/ |
Business logic + state | → Domain |
| Domain | entities/, repositories/ |
Core business rules | No external dependencies |
| Data | repositories/impl/, datasources/ |
Data fetching + persistence | → Domain |
(2) Dependency Rules
- Domain layer: Pure Dart, no Flutter/third-party dependencies
- Data layer: Implements Domain's Repository interfaces
- Application layer: Coordinates Domain and Presentation
- Presentation layer: Only depends on the Application layer
6. Data Modeling
(1) Core Entities
▶ Example
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; please use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
: Domain Entity definitions
// domain/entities/product.dart
// Pure Dart class, no external dependencies
class Product {
final int id;
final String name;
final double price;
final String imageUrl;
final String category;
final double rating;
final int reviewCount;
final int stock;
final String currency;
const Product({
required this.id,
required this.name,
required this.price,
required this.imageUrl,
this.category = 'General',
this.rating = 0.0,
this.reviewCount = 0,
this.stock = 0,
this.currency = 'USD',
});
bool get inStock => stock > 0;
bool get hasDiscount => false; // Extended in sale scenario
}
// domain/entities/cart_item.dart
class CartItem {
final Product product;
final int quantity;
final String? selectedSize;
final String? selectedColor;
const CartItem({
required this.product,
required this.quantity,
this.selectedSize,
this.selectedColor,
});
double get lineTotal => product.price * quantity;
CartItem copyWith({int? quantity, String? selectedSize, String? selectedColor}) =>
CartItem(product: product, quantity: quantity ?? this.quantity,
selectedSize: selectedSize ?? this.selectedSize,
selectedColor: selectedColor ?? this.selectedColor);
}
// domain/entities/address.dart
class Address {
final String name;
final String street;
final String city;
final String state;
final String zip;
final String country;
const Address({
required this.name,
required this.street,
required this.city,
required this.state,
required this.zip,
this.country = 'US',
});
String get fullAddress => '$street, $city, $state $zip, $country';
Map<String, dynamic> toJson() => {
'name': name, 'street': street, 'city': city,
'state': state, 'zip': zip, 'country': country,
};
}
// domain/entities/order.dart
enum OrderStatus { pending, confirmed, shipped, delivered, cancelled }
class Order {
final String id;
final String userId;
final List<CartItem> items;
final double total;
final String currency;
final OrderStatus status;
final DateTime createdAt;
final Address shippingAddress;
const Order({
required this.id,
required this.userId,
required this.items,
required this.total,
this.currency = 'USD',
this.status = OrderStatus.pending,
required this.createdAt,
required this.shippingAddress,
});
}
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; please use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
(2) Firestore Schema Design
firestore/
├── products/{productId}
│ ├── name: string
│ ├── price: number
│ ├── stock: number
│ ├── category: string
│ ├── rating: number
│ ├── imageUrl: string
│ └── currency: string
├── users/{userId}
│ ├── email: string
│ ├── name: string
│ ├── preferences: map
│ │ ├── theme: string
│ │ ├── currency: string
│ │ └── locale: string
│ └── addresses: array
├── orders/{orderId}
│ ├── userId: string
│ ├── items: array<{productId, quantity, price}>
│ ├── total: number
│ ├── currency: string
│ ├── status: string
│ └── createdAt: timestamp
└── reviews/{reviewId}
├── productId: string
├── userId: string
├── rating: number
└── comment: string
```text
```text
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; please use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
7. UI/UX Planning
(1) Page Inventory
| Page | Route | Core Components |
|---|---|---|
| Home | / |
SliverAppBar + GridView + CategoryChips |
| Search | /search |
SearchBar + FilterSheet + ProductList |
| Product Detail | /product/:id |
Hero + SliverAppBar + BottomSheet |
| Cart | /cart |
CartList + QuantitySelector + TotalBar |
| Checkout | /checkout |
AddressForm + PaymentSelector + OrderSummary |
| Order Confirmation | /order/:id |
OrderTimeline + TrackingMap |
| Profile | /profile |
UserCard + OrderHistory + Settings |
| Login | /login |
EmailForm + GoogleButton + AppleButton |
(2) Design Tokens
import 'package:flutter/material.dart';
// Design tokens: unified color/spacing/radius/font constants, carried by ThemeExtension
class ShopDesignTokens {
// Colors
static const primary = Color(0xFF0066CC);
static const secondary = Color(0xFFFF6B35);
static const sale = Color(0xFF4CAF50);
static const discount = Color(0xFFEF5350);
// Spacing
static const xs = 4.0;
static const sm = 8.0;
static const md = 16.0;
static const lg = 24.0;
static const xl = 32.0;
// Radius
static const cardRadius = 12.0;
static const buttonRadius = 8.0;
static const inputRadius = 8.0;
// Typography
static const headlineSize = 24.0;
static const titleSize = 18.0;
static const bodySize = 14.0;
static const captionSize = 12.0;
}
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; please use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
8. Complete Example: Project Directory Structure
shop_app/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── core/
│ │ ├── router/
│ │ │ └── app_router.dart
│ │ ├── network/
│ │ │ ├── dio_client.dart
│ │ │ └── api_exception.dart
│ │ ├── storage/
│ │ │ ├── secure_storage.dart
│ │ │ ├── hive_cache.dart
│ │ │ └── shared_prefs.dart
│ │ ├── theme/
│ │ │ ├── app_theme.dart
│ │ │ └── brand_tokens.dart
│ │ └── platform/
│ │ └── platform_helper.dart
│ ├── domain/
│ │ ├── entities/
│ │ │ ├── product.dart
│ │ │ ├── cart_item.dart
│ │ │ ├── order.dart
│ │ │ └── user.dart
│ │ └── repositories/
│ │ ├── product_repository.dart
│ │ ├── auth_repository.dart
│ │ └── order_repository.dart
│ ├── data/
│ │ ├── repositories/
│ │ │ ├── product_repository_impl.dart
│ │ │ ├── auth_repository_impl.dart
│ │ │ └── order_repository_impl.dart
│ │ ├── datasources/
│ │ │ ├── firestore_datasource.dart
│ │ │ └── hive_datasource.dart
│ │ └── models/
│ │ ├── product_dto.dart
│ │ └── order_dto.dart
│ ├── application/
│ │ ├── notifiers/
│ │ │ ├── auth_notifier.dart
│ │ │ ├── product_notifier.dart
│ │ │ └── cart_notifier.dart
│ │ └── providers/
│ │ └── infrastructure_providers.dart
│ ├── presentation/
│ │ ├── screens/
│ │ │ ├── home_page.dart
│ │ │ ├── detail_page.dart
│ │ │ ├── cart_page.dart
│ │ │ ├── checkout_page.dart
│ │ │ └── login_page.dart
│ │ └── widgets/
│ │ ├── product_card.dart
│ │ ├── quantity_selector.dart
│ │ ├── cart_badge.dart
│ │ └── price_tag.dart
│ └── l10n/
│ ├── app_en.arb
│ ├── app_zh.arb
│ └── app_ja.arb
├── test/
│ ├── domain/
│ ├── application/
│ └── presentation/
├── integration_test/
├── android/
├── ios/
├── web/
├── windows/
├── macos/
├── pubspec.yaml
├── l10n.yaml
└── .github/workflows/
```text
```text
> Output: Run on a local Flutter SDK (Flutter 3.x / Dart 3.x). The Piston server does not have Flutter installed; please use `flutter run` on your local machine for comparison. Actual UI/state may vary slightly by platform.
❓ FAQ
📖 Summary
- User stories drive requirements analysis, organized by role and priority
- Technology selection requires balancing team capability, project scale, and ecosystem maturity
- Clean Architecture four layers: Presentation → Application → Domain → Data
- Domain layer is pure Dart with no external dependencies; Repository interfaces are defined in Domain
- Design tokens unify color/spacing/radius/font, carried by ThemeExtension
📝 Exercises
- Basic (Difficulty ⭐): Write 10 user stories for ShopApp, categorized by P0/P1/P2 priority.
- Intermediate (Difficulty ⭐⭐): Design a Clean Architecture four-layer directory structure, defining Product/Cart/Order entities and corresponding Repository interfaces.
- Challenge (Difficulty ⭐⭐⭐): Complete a full project design document: user stories + technology selection comparison table + Clean Architecture layering diagram + Firestore Schema + page inventory + design tokens.
← Previous Lesson | Next Lesson →
(2) Example: ShopApp Module Dependency Graph
final Map<String, List<String>> moduleDeps = {
'core': [],
'cart': ['core'],
'checkout': ['core', 'cart'],
'profile': ['core'],
'catalog': ['core'],
};
Output:
core: 0 direct dependencies
cart: depends on core
checkout: depends on core, cart
profile: depends on core
catalog: depends on core
(3) Example: Route Table
final Map<String, String> routes = {
'/': 'CatalogPage',
'/cart': 'CartPage',
'/checkout': 'CheckoutPage',
'/profile': 'ProfilePage',
};
Output:
/ -> CatalogPage
/cart -> CartPage
/checkout -> CheckoutPage
/profile -> ProfilePage