25 KiB
25 KiB
Reviews Feature - Architecture Diagram
Clean Architecture Flow
┌─────────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ UI Components │ │
│ │ - ProductTabsSection (_ReviewsTab) │ │
│ │ - WriteReviewPage │ │
│ │ - _ReviewItem widget │ │
│ └───────────────┬───────────────────────────────────────────┘ │
│ │ watches providers │
│ ┌───────────────▼───────────────────────────────────────────┐ │
│ │ Riverpod Providers (reviews_provider.dart) │ │
│ │ - productReviewsProvider(itemId) │ │
│ │ - productAverageRatingProvider(itemId) │ │
│ │ - productReviewCountProvider(itemId) │ │
│ │ - submitReviewProvider │ │
│ │ - deleteReviewProvider │ │
│ └───────────────┬───────────────────────────────────────────┘ │
└──────────────────┼───────────────────────────────────────────────┘
│ calls use cases
┌──────────────────▼───────────────────────────────────────────────┐
│ DOMAIN LAYER │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Use Cases │ │
│ │ - GetProductReviews │ │
│ │ - SubmitReview │ │
│ │ - DeleteReview │ │
│ └───────────────┬───────────────────────────────────────────┘ │
│ │ depends on │
│ ┌───────────────▼───────────────────────────────────────────┐ │
│ │ Repository Interface (ReviewsRepository) │ │
│ │ - getProductReviews() │ │
│ │ - submitReview() │ │
│ │ - deleteReview() │ │
│ └───────────────┬───────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────▼───────────────────────────────────────────┐ │
│ │ Entities │ │
│ │ - Review │ │
│ │ - id, itemId, rating, comment │ │
│ │ - reviewerName, reviewerEmail, reviewDate │ │
│ │ - starsRating (computed: rating * 5) │ │
│ └───────────────────────────────────────────────────────────┘ │
└──────────────────┬───────────────────────────────────────────────┘
│ implemented by
┌──────────────────▼───────────────────────────────────────────────┐
│ DATA LAYER │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Repository Implementation │ │
│ │ ReviewsRepositoryImpl │ │
│ │ - delegates to remote data source │ │
│ │ - converts models to entities │ │
│ │ - sorts reviews by date │ │
│ └───────────────┬───────────────────────────────────────────┘ │
│ │ uses │
│ ┌───────────────▼───────────────────────────────────────────┐ │
│ │ Remote Data Source (ReviewsRemoteDataSourceImpl) │ │
│ │ - makes HTTP requests via DioClient │ │
│ │ - handles response parsing │ │
│ │ - error handling & transformation │ │
│ └───────────────┬───────────────────────────────────────────┘ │
│ │ returns │
│ ┌───────────────▼───────────────────────────────────────────┐ │
│ │ Models (ReviewModel) │ │
│ │ - fromJson() / toJson() │ │
│ │ - toEntity() / fromEntity() │ │
│ │ - handles API response format │ │
│ └───────────────┬───────────────────────────────────────────┘ │
└──────────────────┼───────────────────────────────────────────────┘
│ communicates with
┌──────────────────▼───────────────────────────────────────────────┐
│ EXTERNAL SERVICES │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Frappe/ERPNext API │ │
│ │ - POST /api/method/...item_feedback.get_list │ │
│ │ - POST /api/method/...item_feedback.update │ │
│ │ - POST /api/method/...item_feedback.delete │ │
│ └───────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Data Flow: Fetching Reviews
User opens product detail page
│
▼
ProductTabsSection renders
│
▼
_ReviewsTab watches productReviewsProvider(itemId)
│
▼
Provider executes GetProductReviews use case
│
▼
Use case calls repository.getProductReviews()
│
▼
Repository calls remoteDataSource.getProductReviews()
│
▼
Data source makes HTTP POST to API
│
▼
API returns JSON response
│
▼
Data source parses JSON to List<ReviewModel>
│
▼
Repository converts models to List<Review> entities
│
▼
Repository sorts reviews by date (newest first)
│
▼
Provider returns AsyncValue<List<Review>>
│
▼
_ReviewsTab renders reviews with .when()
│
▼
User sees review list
Data Flow: Submitting Review
User clicks "Write Review" button
│
▼
Navigate to WriteReviewPage
│
▼
User selects stars (1-5) and enters comment
│
▼
User clicks "Submit" button
│
▼
Page validates form:
- Rating: 1-5 stars ✓
- Comment: 20-1000 chars ✓
│
▼
Convert stars to API rating: apiRating = stars / 5.0
│
▼
Call submitReviewProvider.call()
│
▼
Use case validates:
- Rating: 0-1 ✓
- Comment: not empty, 20-1000 chars ✓
│
▼
Use case calls repository.submitReview()
│
▼
Repository calls remoteDataSource.submitReview()
│
▼
Data source makes HTTP POST to API
│
▼
API processes request and returns success
│
▼
Data source returns (void)
│
▼
Use case returns (void)
│
▼
Page invalidates productReviewsProvider(itemId)
│
▼
Page shows success SnackBar
│
▼
Page navigates back to product detail
│
▼
ProductTabsSection refreshes (due to invalidate)
│
▼
User sees updated review list with new review
Rating Scale Conversion Flow
UI Layer (Stars: 1-5)
│
│ User selects 4 stars
│
▼
Convert to API: 4 / 5.0 = 0.8
│
▼
Domain Layer (Rating: 0-1)
│
│ Use case validates: 0 ≤ 0.8 ≤ 1 ✓
│
▼
Data Layer sends: { "rating": 0.8 }
│
▼
API stores: rating = 0.8
│
▼
API returns: { "rating": 0.8 }
│
▼
Data Layer parses: ReviewModel(rating: 0.8)
│
▼
Domain Layer converts: Review(rating: 0.8)
│
│ Entity computes: starsRating = (0.8 * 5).round() = 4
│
▼
UI Layer displays: ⭐⭐⭐⭐☆
Error Handling Flow
User action (fetch/submit/delete)
│
▼
Try block starts
│
▼
API call may throw exceptions:
│
├─► DioException (timeout, connection, etc.)
│ │
│ ▼
│ Caught by _handleDioException()
│ │
│ ▼
│ Converted to app exception:
│ - TimeoutException
│ - NoInternetException
│ - UnauthorizedException
│ - ServerException
│ - etc.
│
├─► ParseException (JSON parsing error)
│ │
│ ▼
│ Rethrown as-is
│
└─► Unknown error
│
▼
UnknownException(originalError, stackTrace)
│
▼
Exception propagates to provider
│
▼
Provider returns AsyncValue.error(exception)
│
▼
UI handles with .when(error: ...)
│
▼
User sees error message
Provider Dependency Graph
dioClientProvider
│
▼
reviewsRemoteDataSourceProvider
│
▼
reviewsRepositoryProvider
│
┌────────────┼────────────┐
▼ ▼ ▼
getProductReviews submitReview deleteReview
Provider Provider Provider
│ │ │
▼ │ │
productReviewsProvider│ │
(family) │ │
│ │ │
┌──────┴──────┐ │ │
▼ ▼ ▼ ▼
productAverage productReview (used directly
RatingProvider CountProvider in UI components)
(family) (family)
Component Interaction Diagram
┌─────────────────────────────────────────────────────────────┐
│ ProductDetailPage │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ ProductTabsSection │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │ │
│ │ │ Specifications│ │ Reviews │ │ (other tab) │ │ │
│ │ │ Tab │ │ Tab │ │ │ │ │
│ │ └──────────────┘ └──────┬───────┘ └─────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────────────────▼───────────────────────┐ │ │
│ │ │ _ReviewsTab │ │ │
│ │ │ │ │ │
│ │ │ ┌───────────────────────────────────────────┐ │ │ │
│ │ │ │ WriteReviewButton │ │ │ │
│ │ │ │ (navigates to WriteReviewPage) │ │ │ │
│ │ │ └───────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌───────────────────────────────────────────┐ │ │ │
│ │ │ │ Rating Overview │ │ │ │
│ │ │ │ - Average rating (4.8) │ │ │ │
│ │ │ │ - Star display (⭐⭐⭐⭐⭐) │ │ │ │
│ │ │ │ - Review count (125 đánh giá) │ │ │ │
│ │ │ └───────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ ┌───────────────────────────────────────────┐ │ │ │
│ │ │ │ _ReviewItem (repeated) │ │ │ │
│ │ │ │ - Avatar │ │ │ │
│ │ │ │ - Reviewer name │ │ │ │
│ │ │ │ - Date (2 tuần trước) │ │ │ │
│ │ │ │ - Star rating (⭐⭐⭐⭐☆) │ │ │ │
│ │ │ │ - Comment text │ │ │ │
│ │ │ └───────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ clicks "Write Review"
▼
┌─────────────────────────────────────────────────────────────┐
│ WriteReviewPage │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Product Info Card (read-only) │ │
│ │ - Product image │ │
│ │ - Product name │ │
│ │ - Product code │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ StarRatingSelector │ │
│ │ ☆☆☆☆☆ → ⭐⭐⭐⭐☆ (4 stars selected) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Comment TextField │ │
│ │ [ ] │ │
│ │ [ Multi-line text input ] │ │
│ │ [ ] │ │
│ │ 50 / 1000 ký tự │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ ReviewGuidelinesCard │ │
│ │ - Be honest and fair │ │
│ │ - Focus on the product │ │
│ │ - etc. │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ [Submit Button] │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ clicks "Submit"
▼
Validates & submits review
│
▼
Shows success SnackBar
│
▼
Navigates back to ProductDetailPage
│
▼
Reviews refresh automatically
State Management Lifecycle
1. Initial State (Loading)
├─► productReviewsProvider returns AsyncValue.loading()
└─► UI shows CustomLoadingIndicator
2. Loading State → Data State
├─► API call succeeds
├─► Provider returns AsyncValue.data(List<Review>)
└─► UI shows review list
3. Data State → Refresh State (after submit)
├─► User submits new review
├─► ref.invalidate(productReviewsProvider)
├─► Provider state reset to loading
├─► API call re-executes
└─► UI updates with new data
4. Error State
├─► API call fails
├─► Provider returns AsyncValue.error(exception)
└─► UI shows error message
5. Empty State (special case of Data State)
├─► API returns empty list
├─► Provider returns AsyncValue.data([])
└─► UI shows "No reviews yet" message
Caching Strategy
Provider State Cache (Riverpod)
│
├─► Auto-disposed when widget unmounted
│ (productReviewsProvider uses AutoDispose)
│
├─► Cache invalidated on:
│ - User submits review
│ - User deletes review
│ - Manual ref.invalidate() call
│
└─► Cache refresh:
- Pull-to-refresh gesture (future enhancement)
- App resume from background (future enhancement)
- Time-based expiry (future enhancement)
HTTP Cache (Dio CacheInterceptor)
│
├─► Reviews NOT cached (POST requests)
│ (only GET requests cached by default)
│
└─► Future: Implement custom cache policy
- Cache reviews for 5 minutes
- Invalidate on write operations
Testing Strategy
Unit Tests
├─► Domain Layer
│ ├─► Use cases
│ │ ├─► GetProductReviews
│ │ ├─► SubmitReview (validates rating & comment)
│ │ └─► DeleteReview
│ └─► Entities
│ └─► Review (starsRating computation)
│
├─► Data Layer
│ ├─► Models (fromJson, toJson, toEntity)
│ ├─► Remote Data Source (API calls, error handling)
│ └─► Repository (model-to-entity conversion, sorting)
│
└─► Presentation Layer
└─► Providers (state transformations)
Widget Tests
├─► _ReviewsTab
│ ├─► Loading state
│ ├─► Empty state
│ ├─► Data state
│ └─► Error state
│
├─► _ReviewItem
│ ├─► Displays correct data
│ ├─► Date formatting
│ └─► Star rendering
│
└─► WriteReviewPage
├─► Form validation
├─► Submit button states
└─► Error messages
Integration Tests
└─► End-to-end flow
├─► Fetch reviews
├─► Submit review
├─► Verify refresh
└─► Error scenarios
This architecture follows:
- ✅ Clean Architecture principles
- ✅ SOLID principles
- ✅ Dependency Inversion (interfaces in domain layer)
- ✅ Single Responsibility (each class has one job)
- ✅ Separation of Concerns (UI, business logic, data separate)
- ✅ Testability (all layers mockable)