// 🎯 STANDARDIZED RIVERPOD SERVICE TEMPLATE
//
// Use this template to create consistent Riverpod services throughout the app.
// This ensures all services follow the same patterns for initialization, testing, and usage.
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
part 'service_template.g.dart';
// =============================================================================
// 1. STATE CLASS - Always immutable with copyWith and convenience getters
// =============================================================================
class ServiceTemplateState {
final bool isLoading;
final String? error;
final DateTime? lastUpdated;
// Add your specific state properties here
const ServiceTemplateState({
this.isLoading = false,
this.error,
this.lastUpdated,
});
ServiceTemplateState copyWith({
bool? isLoading,
String? error,
DateTime? lastUpdated,
}) {
return ServiceTemplateState(
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
lastUpdated: lastUpdated ?? this.lastUpdated,
);
}
// Convenience getters - always include these
bool get hasError => error != null;
bool get isIdle => !isLoading && error == null;
bool get isReady => !isLoading && error == null;
}
// =============================================================================
// 2. MAIN SERVICE - Always use @riverpod class pattern
// =============================================================================
@riverpod
class ServiceTemplate extends _$ServiceTemplate {
@override
ServiceTemplateState build() {
// Initialize with default state
return const ServiceTemplateState();
}
// =============================================================================
// PUBLIC API METHODS - Always async with consistent error handling
// =============================================================================
/// Standard async operation pattern
Future<void> performAction() async {
state = state.copyWith(isLoading: true, error: null);
try {
// TODO: Implement your business logic 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(),
);
rethrow; // Optional: rethrow if caller needs to handle
}
}
/// Standard sync operation pattern
void updateSetting(String value) {
try {
// TODO: Implement your logic here
state = state.copyWith(
lastUpdated: DateTime.now(),
);
} catch (e) {
state = state.copyWith(error: e.toString());
}
}
// =============================================================================
// UTILITY METHODS - Standard helper methods all services should have
// =============================================================================
/// Clear error state
void clearError() {
state = state.copyWith(error: null);
}
/// Reset to initial state
void reset() {
state = const ServiceTemplateState();
}
/// Force refresh/reload
Future<void> refresh() async {
await performAction(); // Or implement specific refresh logic
}
// =============================================================================
// CONVENIENCE GETTERS - Easy access to common state properties
// =============================================================================
bool get isLoading => state.isLoading;
String? get error => state.error;
bool get hasError => state.hasError;
bool get isIdle => state.isIdle;
DateTime? get lastUpdated => state.lastUpdated;
}
// =============================================================================
// 3. CONVENIENCE PROVIDERS - Quick access to commonly used state parts
// =============================================================================
/// Quick access to loading state
@riverpod
bool serviceTemplateIsLoading(Ref ref) {
return ref.watch(serviceTemplateProvider).isLoading;
}
/// Quick access to error state
@riverpod
String? serviceTemplateError(Ref ref) {
return ref.watch(serviceTemplateProvider).error;
}
/// Quick access to idle state (not loading, no error)
@riverpod
bool serviceTemplateIsReady(Ref ref) {
return ref.watch(serviceTemplateProvider).isReady;
}
// =============================================================================
// 4. BRIDGE INTEGRATION (if needed for external services)
// =============================================================================
@riverpod
ServiceTemplateBridge serviceTemplateBridge(Ref ref) {
return ServiceTemplateBridge();
}
class ServiceTemplateBridge {
/// External service integration
Future<String> callExternalApi() async {
// TODO: Implement actual external service call
await Future.delayed(const Duration(milliseconds: 100));
return 'result';
}
}
// =============================================================================
// 5. TESTING SUPPORT - Mock implementations and test helpers
// =============================================================================
/// Mock service for testing - extends the real service
class MockServiceTemplate extends ServiceTemplate {
MockServiceTemplate() : super();
@override
ServiceTemplateState build() {
return const ServiceTemplateState();
}
/// Test helper to set specific state
void setMockState(ServiceTemplateState newState) {
state = newState;
}
/// Test helper to simulate loading
void setLoading(bool loading) {
state = state.copyWith(isLoading: loading);
}
/// Test helper to simulate error
void setError(String error) {
state = state.copyWith(error: error, isLoading: false);
}
}
// =============================================================================
// 6. TEST UTILITIES - Standard test helpers for all services
// =============================================================================
class ServiceTemplateTestHelper {
/// Create test container for unit tests
static ProviderContainer createTestContainer({
List<Override> overrides = const [],
}) {
return ProviderContainer(
overrides: [
// Add standard test overrides here if needed
...overrides,
],
);
}
/// Create test container with mock service
static ProviderContainer createMockContainer() {
final mockService = MockServiceTemplate();
return ProviderContainer(
overrides: [
serviceTemplateProvider.overrideWith(() => mockService),
],
);
}
/// Create test container for production testing (UncontrolledProviderScope)
static ProviderContainer createProductionTestContainer({
List<Override> overrides = const [],
}) {
return ProviderContainer(
overrides: overrides,
);
}
}
// =============================================================================
// USAGE EXAMPLES:
// =============================================================================
//
// In widgets:
// final service = ref.watch(serviceTemplateProvider);
// final isLoading = ref.watch(serviceTemplateIsLoadingProvider);
//
// await ref.read(serviceTemplateProvider.notifier).performAction();
//
// In tests:
// final container = ServiceTemplateTestHelper.createTestContainer();
// final service = container.read(serviceTemplateProvider.notifier);
// await service.performAction();
// expect(container.read(serviceTemplateProvider).isReady, true);
//
// =============================================================================