A comprehensive Flutter project demonstrating clean MVVM (Model-View-ViewModel) architecture using Riverpod for state management. This project showcases best practices for building scalable, maintainable, and testable Flutter applications.
This project follows the MVVM pattern with clear separation of concerns:
- Model: Data models and business logic
- View: UI components and widgets
- ViewModel: State management and business logic coordination
- Services: API calls, data persistence, and external integrations
lib/
โโโ core/
โ โโโ either.dart # Result handling utility
โโโ services/
โ โโโ network_service.dart # HTTP client and API communication
โ โโโ animal_service.dart # Domain-specific API services
โ โโโ error_handling.dart # Centralized error handling
โโโ ui/
โโโ enums/
โ โโโ request.dart # HTTP request types
โโโ extensions/
โ โโโ request_extension.dart # Request utility extensions
โโโ views/
โโโ home/
โโโ home_view.dart # UI presentation layer
โโโ home_view_model.dart # Business logic and state management
โโโ home_state.dart # State model definitions
The NetworkService class provides a centralized HTTP client using Dio:
final networkServiceProvider = Provider<NetworkService>((ref) => NetworkService());
class NetworkService with ErrorHandling {
// Configured Dio instance with base URL and interceptors
// Handles all HTTP communications
// Includes debug logging and error handling
}Features:
- Centralized HTTP configuration
- Debug logging in development mode
- Built-in error handling with custom error processing
- Timeout configuration
- JSON serialization/deserialization
Domain-specific services handle business logic and API interactions:
final animalServiceProvider = Provider<AnimalService>((ref) => AnimalService(
networkService: ref.watch(networkServiceProvider),
));
class AnimalService {
// Domain-specific API calls
// Returns Either<Exception, Data> for robust error handling
}Key Benefits:
- Separation of concerns between network layer and business logic
- Type-safe error handling with Either pattern
- Dependency injection through Riverpod
- Easy testing and mocking
class HomeView extends ConsumerStatefulWidget {
// UI-only concerns
// Observes ViewModel state changes
// Dispatches user actions to ViewModel
}final homeViewModelProvider = StateNotifierProvider.autoDispose<HomeViewModel, HomeState>(
(ref) => HomeViewModel(ref)
);
class HomeViewModel extends StateNotifier<HomeState> {
// Business logic coordination
// State management
// Service orchestration
}class HomeState {
final UiState uiState;
final List<String> houndList;
final String? errorMessage;
// Immutable state with copyWith pattern
// Clear state definitions
}- Uses Riverpod for dependency injection and state management
- Automatic state updates trigger UI rebuilds
- Memory-efficient with
autoDisposeproviders
- Either pattern for functional error handling
- Centralized error processing
- User-friendly error messages
- Debug information in development
- Clear boundaries between layers
- Single responsibility principle
- Easy to test and maintain
- Strong typing throughout the application
- Compile-time error detection
- Better IDE support and refactoring
The application uses a comprehensive state management approach:
enum UiState { idle, loading, success, error }State Flow:
- Idle: Initial state
- Loading: Data fetching in progress
- Success: Data loaded successfully
- Error: Error occurred during operation
UI Rendering:
switch(state.uiState) {
UiState.loading => CircularProgressIndicator(),
UiState.success => DataWidget(data: state.data),
UiState.error => ErrorWidget(message: state.errorMessage),
_ => DefaultWidget(),
}Add these dependencies to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.4.9
dio: ^5.3.2
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.4.2
build_runner: ^2.4.7git clone <repository-url>
cd mvvm_riverpod
flutter pub getflutter runThis project is actively being developed with exciting features planned for the future:
- Unit Testing: Comprehensive ViewModel testing with mock services
- Integration Testing: End-to-end testing of complete user flows
- Widget Testing: Individual UI component testing
- Test Coverage: Automated coverage reporting and CI/CD integration
- AsyncNotifier: Enhanced async state management patterns
- Family Providers: Dynamic provider instances for lists and parameterized data
- Scoped Providers: Feature-scoped state management
- Provider Observers: Advanced debugging and logging capabilities
- Code Generation: Riverpod code generation for better developer experience
- Repository Pattern: Data layer abstraction with caching strategies
- Use Cases: Business logic isolation with clean architecture principles
- Dependency Injection: Advanced DI patterns with multiple environments
- Error Recovery: Automatic retry mechanisms and offline support
- Performance Monitoring: Real-time performance metrics and optimization
- Code Templates: VS Code snippets for rapid MVVM development
- Linting Rules: Custom lint rules for architecture compliance
- Documentation: Interactive API documentation with examples
- Migration Guides: Step-by-step guides for adopting patterns
Stay tuned for these exciting updates! ๐
autoDisposeproviders automatically clean up unused state- Efficient widget rebuilds through granular state observation
- Lazy initialization of services
- Request/response logging in debug mode only
- Configurable timeout settings
- Error retry mechanisms (can be implemented)
User Action โ ViewModel โ Service โ Network โ API
โ โ
View โ State Update โ ViewModel โ Service โ Response
- Dependency Injection: All dependencies injected through Riverpod providers
- Error Boundary: Comprehensive error handling at every layer
- Immutable State: State objects are immutable with copyWith pattern
- Single Source of Truth: State centralized in ViewModels
- Testability: Easy to mock and test individual components
- Scalability: Clear patterns for adding new features
- Create State Model
class FeatureState {
final UiState uiState;
final List<DataModel> items;
final String? errorMessage;
FeatureState({required this.uiState, required this.items, this.errorMessage});
FeatureState copyWith({...}) => FeatureState(...);
}- Create ViewModel
final featureViewModelProvider = StateNotifierProvider.autoDispose<FeatureViewModel, FeatureState>(
(ref) => FeatureViewModel(ref)
);
class FeatureViewModel extends StateNotifier<FeatureState> {
final Ref _ref;
FeatureViewModel(this._ref) : super(FeatureState.initial());
Future<void> loadData() async {
// Implementation
}
}- Create View
class FeatureView extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(featureViewModelProvider);
return Scaffold(
body: switch(state.uiState) {
// Handle different states
},
);
}
}- Create Service Class
final newServiceProvider = Provider<NewService>((ref) => NewService(
networkService: ref.watch(networkServiceProvider),
));
class NewService {
final NetworkService networkService;
NewService({required this.networkService});
Future<Either<Exception, DataModel>> fetchData() async {
// Implementation
}
}- Fork the repository
- Create a feature branch (
git branch -M feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
If you have any questions or need help with implementation, please:
- Check the existing issues
- Create a new issue with a detailed description
- Join our community discussions
Built with โค๏ธ using Flutter and Riverpod