// 🏆 RIVERPOD SERVICE PATTERN
//
// This file defines the standard pattern for all Riverpod services/providers/bridges
// in the FlipDare application. All services should follow this consistent structure.
//
// Key principles:
// - Consistent naming conventions
// - Standardized initialization
// - Unified testing approach
// - Support for both ProviderScope and UncontrolledProviderScope
// - Auto-generation with riverpod_generator
// - Clean state management
// - Proper error handling
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
// This file demonstrates the standard pattern - do not implement actual logic here
part '_provider_pattern.g.dart';
// =============================================================================
// 1. STATE CLASSES
// =============================================================================
/// State class pattern - always immutable with copyWith
class ServiceState {
final bool isLoading;
final String? error;
final DateTime? lastUpdated;
const ServiceState({
this.isLoading = false,
this.error,
this.lastUpdated,
});
ServiceState copyWith({
bool? isLoading,
String? error,
DateTime? lastUpdated,
}) {
return ServiceState(
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
lastUpdated: lastUpdated ?? this.lastUpdated,
);
}
// Convenience getters
bool get hasError => error != null;
bool get isIdle => !isLoading && error == null;
}
// =============================================================================
// 2. SERVICE CLASSES - Always use @riverpod annotation
// =============================================================================
/// Main service pattern using riverpod_generator
@riverpod
class Service extends _$Service {
@override
ServiceState build() {
// Initialize with default state
return const ServiceState();
}
/// Standard method pattern - async with error handling
Future<void> performAction() async {
state = state.copyWith(isLoading: true, error: null);
try {
// Perform actual work here
await Future.delayed(const Duration(milliseconds: 100));
state = state.copyWith(
isLoading: false,
lastUpdated: DateTime.now(),
);
} catch (e) {
state = state.copyWith(
isLoading: false,
error: e.toString(),
);
}
}
/// Clear error state
void clearError() {
state = state.copyWith(error: null);
}
/// Reset to initial state
void reset() {
state = const ServiceState();
}
}
// =============================================================================
// 3. CONVENIENCE PROVIDERS - Always use @riverpod annotation
// =============================================================================
/// Quick access to loading state
@riverpod
bool serviceIsLoading(Ref ref) {
return ref.watch(serviceProvider).isLoading;
}
/// Quick access to error state
@riverpod
String? serviceError(Ref ref) {
return ref.watch(serviceProvider).error;
}
/// Quick access to idle state
@riverpod
bool serviceIsIdle(Ref ref) {
return ref.watch(serviceProvider).isIdle;
}
// =============================================================================
// 4. BRIDGE CLASSES - For external service integration
// =============================================================================
/// Bridge pattern for external services
@riverpod
ServiceBridge serviceBridge(Ref ref) {
return ServiceBridge();
}
class ServiceBridge {
/// Standard bridge method pattern
Future<String> performExternalAction() async {
// TODO: Implement actual external service call
await Future.delayed(const Duration(milliseconds: 100));
return 'result';
}
}
// =============================================================================
// 5. TESTING UTILITIES
// =============================================================================
/// Mock service for testing
class MockService extends Service {
MockService() : super();
@override
ServiceState build() {
return const ServiceState();
}
void setMockState(ServiceState newState) {
state = newState;
}
}
/// Test helper to create provider container with overrides
class ServiceTestHelper {
static ProviderContainer createTestContainer({
List<Override> overrides = const [],
}) {
return ProviderContainer(
overrides: [
// Add default test overrides here
...overrides,
],
);
}
static ProviderContainer createTestContainerWithMockService() {
final mockService = MockService();
return ProviderContainer(
overrides: [
serviceProvider.overrideWith(() => mockService),
],
);
}
}
// =============================================================================
// NAMING CONVENTIONS:
// =============================================================================
//
// Services: XxxService (class), xxxServiceProvider (generated)
// States: XxxServiceState
// Bridges: XxxBridge (class), xxxBridgeProvider (generated)
// Convenience: xxxIsLoading, xxxError, xxxData, etc.
// Tests: XxxServiceTest, runXxxServiceUnitTests()
//
// =============================================================================
// FILE STRUCTURE:
// =============================================================================
//
// provider/
// ├── xxx/
// │ ├── xxx_service.dart // Main service
// │ ├── xxx_service_state.dart // State class
// │ └── xxx_service.g.dart // Generated file
// └── _provider_pattern.dart // This pattern file
//
// provider_bridge/
// ├── xxx_bridge.dart // Bridge class
// └── bridge_registration.dart // All bridge providers
//
// test/unit/provider/
// └── xxx_service_unit.dart // Unit tests
//
// =============================================================================