aero_mvvm
A lightweight, zero-dependency, high-performance MVVM framework for Flutter.
Built entirely on Flutter's native ChangeNotifier and ListenableBuilder, Aero MVVM provides a clean and scalable Model-View-ViewModel architecture without code generation, external dependencies, or unnecessary complexity.
Whether you're building small apps or enterprise-scale solutions, Aero MVVM keeps your business logic separate from your UI while delivering smooth performance and granular state updates.
β¨ Features
- π Zero Dependencies β Built entirely using Flutter and Dart.
- β‘ Zero Code Generation β No
build_runner, no generated files. - π§© Clean MVVM Architecture β Separate UI, business logic, and state management.
- π― Granular UI Updates β Rebuild only the widgets that actually change.
- π Built-in Async Support β Handle loading, success, and error states effortlessly.
- π£οΈ Context-Free Logic β Perform navigation and business operations without passing
BuildContext. - π± Smooth List Performance β Optimized for large and infinite scrolling lists.
- π’ Enterprise-Friendly Design β Inspired by proven MVVM patterns from WPF and .NET MAUI.
π¦ Installation
Add Aero MVVM to your project:
flutter pub add aero_mvvm
Import the package:
import 'package:aero_mvvm/aero_mvvm.dart';
Getting Started
1. Basic ViewModel
Create a ViewModel containing your business logic.
CounterViewModel
class CounterViewModel extends ViewModel {
int count = 0;
void increment() {
count++;
rebuild();
}
}
CounterScreen
class CounterScreen extends StatelessWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context) {
return ViewModelBuilder<CounterViewModel>(
viewModelBuilder: () => CounterViewModel(),
builder: (context, vm) {
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: Text(
'Count: ${vm.count}',
style: const TextStyle(fontSize: 24),
),
),
floatingActionButton: FloatingActionButton(
onPressed: vm.increment,
child: const Icon(Icons.add),
),
);
},
);
}
}
2. AsyncViewModel
Handle loading and error states automatically.
LoginViewModel
class LoginViewModel extends AsyncViewModel {
Future<void> login(String email, String password) async {
await runAsync(() async {
await apiService.authenticate(email, password);
NavigationService.replaceWith('/dashboard');
});
}
}
LoginScreen
class LoginScreen extends StatelessWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context) {
return ViewModelBuilder<LoginViewModel>(
viewModelBuilder: () => LoginViewModel(),
builder: (context, vm) {
if (vm.isBusy) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (vm.hasError) {
return Center(
child: Text(
vm.errorMessage ?? 'Something went wrong',
),
);
}
return Center(
child: ElevatedButton(
onPressed: () {
vm.login(
"user@test.com",
"password123",
);
},
child: const Text('Login'),
),
);
},
);
}
}
3. Multiple ViewModels
Listen to multiple ViewModels within a single widget.
class CheckoutScreen extends StatelessWidget {
const CheckoutScreen({super.key});
@override
Widget build(BuildContext context) {
return MultiViewModelBuilder(
viewModels: [
authViewModel,
cartViewModel,
],
builder: (context) {
return Column(
children: [
Text(
'User: ${authViewModel.userName}',
),
Text(
'Items: ${cartViewModel.itemCount}',
),
],
);
},
);
}
}
Architecture Overview
Aero MVVM follows a simple structure:
lib/
βββ views/
β βββ home_screen.dart
β
βββ viewmodels/
β βββ home_viewmodel.dart
β
βββ models/
β βββ user.dart
β
βββ services/
β βββ api_service.dart
β
βββ main.dart
Responsibilities
View
- Displays UI.
- Delegates actions to ViewModel.
- Contains no business logic.
ViewModel
- Holds state.
- Executes business logic.
- Notifies UI when changes occur.
Model
- Represents application data.
Service
- Handles APIs, storage, databases, and external integrations.
Performance Tips
Smart List Rendering
For large or dynamic lists, avoid wrapping the entire ListView in a single ViewModelBuilder.
β Avoid
ViewModelBuilder(
builder: (context, vm) {
return ListView.builder(
itemCount: vm.items.length,
itemBuilder: (_, index) {
return ItemWidget(vm.items[index]);
},
);
},
);
β Recommended
Give each row its own ViewModel.
ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) {
return ViewModelBuilder<ItemViewModel>(
viewModelBuilder: () => ItemViewModel(items[index]),
builder: (context, vm) {
return ItemTile(vm);
},
);
},
);
This ensures only the modified item rebuilds, resulting in extremely smooth scrolling and minimal rendering overhead.
Why Aero MVVM?
| Feature | Aero MVVM | Traditional Solutions |
|---|---|---|
| Zero Dependencies | β | β |
| Code Generation | β | Often Required |
| MVVM Architecture | β | Varies |
| Async State Handling | β | Manual Setup |
| Granular Rebuilds | β | Depends |
| Learning Curve | Low | MediumβHigh |
| Flutter Native | 100% | Varies |
Contributing
Contributions are welcome.
If you discover a bug, have an improvement idea, or want to add new functionality, please open an issue or submit a pull request.
License
This project is licensed under the MIT License.
Built for Flutter developers who want clean architecture, predictable state management, and maximum performance without unnecessary dependencies.