📚 Turbo MVVM - MVVM State Management for Flutter
Turbo MVVM is a lightweight and intuitive MVVM (Model-View-ViewModel) state management solution for Flutter, originally inspired by the FilledStacks stacked package. It simplifies managing view logic, state, and lifecycle in your Flutter applications.

Table of Contents
- Features
- Installation
- Core Concepts
- Usage Guide
- Example Project
- Dependencies
- Contributing
- License
- Support
Features
- Easy ViewModel Lifecycle Management: Automatic handling of
initialiseanddisposemethods. - Reactive UI Updates: Widgets rebuild efficiently when ViewModel state changes using
rebuild()orValueNotifier. - State Management: Built-in support for managing
isInitialised,isBusy, andhasErrorstates. - Context Access: Safe access to
BuildContextwithin ViewModels. - Argument Passing: Simple mechanism to pass arguments to ViewModels during initialization.
- Global Busy Indicator: Centralized
BusyServicefor managing application-wide busy states. - Helper Mixins: Utility mixins like
BusyManagement,ErrorManagement, andViewModelHelpers. - Lightweight and Intuitive: Designed to be easy to learn and integrate.
Installation
-
Add
turbo_mvvmto yourpubspec.yamlfile:dependencies: flutter: sdk: flutter turbo_mvvm: ^1.1.0 -
Install the package by running:
flutter pub get -
Import the package in your Dart files:
import 'package:turbo_mvvm/turbo_mvvm.dart';
Core Concepts
TurboViewModel
The heart of the Turbo MVVM package. Your ViewModels should extend TurboViewModel<A>, where A is the type of arguments you want to pass to the ViewModel.
Key features:
initialise(): Called once when the ViewModel is created. Ideal for setting up data, listeners, etc.dispose(): Called when the ViewModel is no longer needed. Clean up resources here.rebuild(): Notifies listeners (typically theTurboViewModelBuilder) to rebuild the UI.isMounted: A boolean getter to check if the associated View (widget) is currently in the widget tree.context: Provides safe access to theBuildContext.arguments: Holds arguments passed viaTurboViewModelBuilder.isInitialised: AValueListenable<bool>that indicates ifinitialise()has completed.
TurboViewModelBuilder
A widget that builds and provides a TurboViewModel to the widget tree. It listens to the ViewModel and rebuilds its child widgets when rebuild() is called or when other ValueNotifiers within the ViewModel change.
Key parameters:
viewModelBuilder: A function that returns an instance of your ViewModel.builder: A function that builds your UI, receiving thecontext,model(your ViewModel instance),isInitialisedstatus, and an optionalchild.argumentBuilder(optional): A function to provide arguments to your ViewModel'sinitialisemethod.isReactive(default:true): If true, the builder will rebuild whennotifyListeners()(called byrebuild()) is invoked on the ViewModel.shouldDispose(default:true): If true, the ViewModel'sdispose()method will be called automatically.onDispose(optional): A callback executed when the ViewModel is disposed.
Mixins
Turbo MVVM provides several mixins to add common functionalities to your ViewModels:
BusyManagement: AddsisBusy(ValueListenable<bool>),busyTitle,busyMessage, andsetBusy()method for managing local busy states within a ViewModel.ErrorManagement: AddshasError(ValueListenable<bool>),errorTitle,errorMessage, andsetError()method for managing local error states.ViewModelHelpers: Provides utility methods likewait()(for delays) andaddPostFrameCallback().BusyServiceManagement: Integrates with the globalBusyServiceto manage application-wide busy states.
BusyService
A singleton service for managing a global busy state. This is useful for showing an overlay loading indicator across the entire app.
BusyService.instance(): Access the singleton instance.BusyService.initialise(): Configure default busy message, title, type, and timeout.setBusy(bool isBusy, ...): Sets the global busy state.isBusyListenable: AValueListenable<BusyModel>to listen for changes in the global busy state.BusyModel: ContainsisBusy,busyTitle,busyMessage,busyType, andpayload.BusyType: Enum to control the appearance of the busy indicator (e.g.,indicator,indicatorBackdrop).
Usage Guide
Here's a step-by-step guide to using Turbo MVVM:
1. Create your ViewModel
Extend TurboViewModel and add your business logic, state variables, and any desired mixins.
import 'package:flutter/foundation.dart';
import 'package:turbo_mvvm/turbo_mvvm.dart';
class MyViewModel extends TurboViewModel<String> // String is the argument type
with BusyManagement, ErrorManagement {
final ValueNotifier<int> _counter = ValueNotifier(0);
ValueListenable<int> get counter => _counter;
String? _greeting;
String? get greeting => _greeting;
@override
Future<void> initialise() async {
_greeting = "Hello, ${arguments}!";
setBusy(true, message: "Loading data...");
await Future.delayed(const Duration(seconds: 2));
_counter.value = 10;
setBusy(false);
super.initialise();
debugPrint("MyViewModel Initialised with argument: $arguments");
}
void incrementCounter() {
_counter.value++;
}
void performFailableOperation() async {
setBusy(true);
setError(false);
try {
await Future.delayed(const Duration(seconds: 1));
throw Exception("Something went wrong!");
} catch (e, s) {
debugPrintStack(label: e.toString(), stackTrace: s);
setError(true, title: "Error", message: e.toString());
} finally {
setBusy(false);
}
}
@override
void dispose() {
_counter.dispose();
disposeBusyManagement();
disposeErrorManagement();
debugPrint("MyViewModel Disposed");
super.dispose();
}
}
2. Connect ViewModel to your View
Use TurboViewModelBuilder in your widget to provide and react to your ViewModel.
import 'package:flutter/material.dart';
import 'package:turbo_mvvm/turbo_mvvm.dart';
class MyView extends StatelessWidget {
const MyView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return TurboViewModelBuilder<MyViewModel>(
viewModelBuilder: () => MyViewModel(),
argumentBuilder: () => "World",
builder: (context, model, isInitialised, child) {
if (!isInitialised) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
appBar: AppBar(title: Text(model.greeting ?? "Turbo MVVM Example")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ValueListenableBuilder<int>(
valueListenable: model.counter,
builder: (context, count, _) {
return Text('Counter: $count', style: Theme.of(context).textTheme.headlineMedium);
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: model.incrementCounter,
child: const Text('Increment Counter'),
),
const SizedBox(height: 20),
ValueListenableBuilder<bool>(
valueListenable: model.isBusy,
builder: (context, isBusy, _) {
if (isBusy) {
return Column(
children: [
const CircularProgressIndicator(),
if (model.busyMessage != null) ...[
const SizedBox(height: 8),
Text(model.busyMessage!),
]
],
);
}
return const SizedBox.shrink();
},
),
const SizedBox(height: 20),
ValueListenableBuilder<bool>(
valueListenable: model.hasError,
builder: (context, hasError, _) {
if (hasError) {
return Text(
model.errorMessage ?? 'An unknown error occurred.',
style: const TextStyle(color: Colors.red),
);
}
return const SizedBox.shrink();
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: model.performFailableOperation,
child: const Text('Perform Failable Operation'),
),
],
),
),
);
},
);
}
}
3. Accessing ViewModel Properties and Methods
Inside the builder function of TurboViewModelBuilder, access your ViewModel directly and use ValueListenableBuilder for fine-grained updates.
4. Managing Busy State (Local)
The BusyManagement mixin provides:
isBusy: AValueListenable<bool>for the busy state.setBusy(bool isBusy, {String? title, String? message}): Call this to update the busy state.
5. Handling Errors (Local)
The ErrorManagement mixin provides:
hasError: AValueListenable<bool>for the error state.setError(bool hasError, {String? title, String? message}): Call this to update the error state.
6. Global Busy State with BusyService
For application-wide loading indicators:
void main() {
BusyService.initialise(
busyMessageDefault: "Please wait...",
busyTypeDefault: BusyType.indicatorBackdrop,
timeoutDurationDefault: const Duration(seconds: 30),
);
runApp(MyApp());
}
Use BusyServiceManagement mixin or BusyService.instance() in ViewModels, and listen in your root widget’s builder:
class MyApp extends StatelessWidget { ... }
7. Passing Arguments to ViewModel
Pass arguments with argumentBuilder and access via arguments in initialise().
Example Project
Check the /example directory for a complete Flutter application demonstrating Turbo MVVM's features.
Dependencies
provider: Used internally byTurboViewModelBuilderfor efficient state propagation.
Contributing
Contributions are welcome! Please open issues or pull requests on our GitHub repository.
License
This package is licensed under the BSD 3-Clause License. See the LICENSE file for details.
Support
If you have any questions or need help, feel free to contact us through codaveto.com or open an issue on GitHub.
Libraries
- data/constants/turbo_mvvm_defaults
- data/enums/t_busy_type
- data/mixins/t_busy_management
- data/mixins/t_busy_service_management
- data/mixins/t_error_management
- data/mixins/t_view_model_helpers
- data/models/t_base_view_model
- data/models/t_busy_model
- services/t_busy_service
- turbo_mvvm
- widgets/t_view_model_widget