turbo_mvvm 1.3.0
turbo_mvvm: ^1.3.0 copied to clipboard
A lightweight MVVM state management solution inspired by the FilledStacks Stacked package.
Turbo MVVM #
A lightweight MVVM state management solution for Flutter, inspired by the FilledStacks stacked package. It simplifies managing view logic, state, and lifecycle in your Flutter applications.
Table of Contents #
Features #
- ViewModel Lifecycle Management: Automatic handling of
initialiseanddisposemethods. - Reactive UI Updates: Widgets rebuild efficiently when ViewModel state changes using
rebuild()orValueNotifier. - Event-Driven ViewModels: Sequential event processing via
TBaseEventViewModelwith stream-based queue. - Standalone Event Management: Use
TEventManagementoutside of ViewModels for event-driven logic. - 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
TBusyServicefor managing application-wide busy states. - Helper Mixins: Utility mixins like
TBusyManagement,TErrorManagement, andTViewModelHelpers.
Installation #
Add turbo_mvvm to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
turbo_mvvm: ^1.3.0
Install the package:
flutter pub get
Import it:
import 'package:turbo_mvvm/turbo_mvvm.dart';
Core Concepts #
TBaseViewModel #
The base class for all ViewModels. Extend TBaseViewModel<A> where A is the type of arguments passed to the ViewModel.
Key features:
initialise(): Called once when the ViewModel is created. Set up data and listeners here.dispose(): Called when the ViewModel is removed. Clean up resources here.rebuild(): Notifies listeners (theTViewModelBuilder) to rebuild the UI.isMounted: Whether the parent widget is still in the widget tree.context: Safe access to theBuildContext.arguments: Arguments passed viaTViewModelBuilder.isInitialised: AValueListenable<bool>indicating initialisation completion.
TViewModelBuilder #
A widget that creates and provides a TBaseViewModel to the widget tree. It listens to the ViewModel and rebuilds when rebuild() is called.
Key parameters:
viewModelBuilder: Returns an instance of your ViewModel.builder: Builds the UI withcontext,model,isInitialised, and optionalchild.argumentBuilder(optional): Provides arguments toinitialise.isReactive(default:true): Rebuilds onnotifyListeners().shouldDispose(default:true): Auto-callsdispose()on removal.minBusyDuration(optional): Shows global busy overlay during initialisation for at least this duration.onDispose(optional): Callback when the ViewModel is disposed.
TBaseEventViewModel #
An event-driven ViewModel that processes events sequentially via a stream-based queue. Extend TBaseEventViewModel<ARGUMENTS, EVENT> where EVENT is an enum or sealed class representing your events.
Key features:
events: Declare the set of events this ViewModel handles.onEvent(EVENT): Return a handler for each event.emit(EVENT): Fire an event for processing.emitAsync<RESULT>(EVENT): Fire an event and await its result.- Events are processed sequentially via an internal queue.
- Error handling via
onEventError,onStreamError, andonDoneoverrides.
TEventManagement #
A standalone abstract class providing the same event-driven architecture as TBaseEventViewModel, but without requiring a ViewModel. Use this when you need sequential event processing in services or other non-widget classes.
Mixins #
TBusyManagement: AddsisBusy(ValueListenable<bool>),busyTitle,busyMessage, andsetBusy()for local busy states.TErrorManagement: AddshasError(ValueListenable<bool>),errorTitle,errorMessage, andsetError()for local error states.TViewModelHelpers: Provideswait()(delay) andaddPostFrameCallback().TBusyServiceManagement: Integrates with the globalTBusyServicefor application-wide busy states.
TBusyService #
A singleton service for managing a global busy state. Useful for showing an overlay loading indicator across the entire app.
TBusyService.instance(): Access the singleton.TBusyService.initialise(): Configure default message, title, type, and timeout.setBusy(bool isBusy, ...): Set the global busy state.isBusyListenable: AValueListenable<TBusyModel>for changes.TBusyModel: ContainsisBusy,busyTitle,busyMessage,busyType, andpayload.TBusyType: Enum controlling the indicator appearance (indicator,indicatorBackdrop, etc.).
Usage Guide #
1. Create your ViewModel #
Extend TBaseViewModel and add your business logic with desired mixins.
import 'package:flutter/foundation.dart';
import 'package:turbo_mvvm/turbo_mvvm.dart';
class MyViewModel extends TBaseViewModel<String>
with TBusyManagement, TErrorManagement {
final ValueNotifier<int> _counter = ValueNotifier(0);
ValueListenable<int> get counter => _counter;
String? _greeting;
String? get greeting => _greeting;
@override
Future<void> initialise({bool doSetInitialised = true}) async {
_greeting = "Hello, $arguments!";
setBusy(true, message: "Loading data...");
await Future.delayed(const Duration(seconds: 2));
_counter.value = 10;
setBusy(false);
super.initialise(doSetInitialised: doSetInitialised);
}
void incrementCounter() {
_counter.value++;
}
@override
FutureOr<void> dispose() {
_counter.dispose();
disposeBusyManagement();
disposeErrorManagement();
super.dispose();
}
}
2. Connect ViewModel to your View #
Use TViewModelBuilder in your widget.
import 'package:flutter/material.dart';
import 'package:turbo_mvvm/turbo_mvvm.dart';
class MyView extends StatelessWidget {
const MyView({super.key});
@override
Widget build(BuildContext context) {
return TViewModelBuilder<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")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
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'),
),
],
),
),
);
},
);
}
}
3. Event-Driven ViewModel #
Use TBaseEventViewModel for ViewModels that process discrete events sequentially.
import 'package:turbo_mvvm/turbo_mvvm.dart';
enum CounterEvent { increment, decrement, reset }
class CounterViewModel extends TBaseEventViewModel<void, CounterEvent>
with TBusyManagement {
int _count = 0;
int get count => _count;
@override
Set<CounterEvent> get events => CounterEvent.values.toSet();
@override
TEventHandler<CounterEvent> onEvent(CounterEvent event) {
return switch (event) {
CounterEvent.increment => (_) async {
_count++;
rebuild();
},
CounterEvent.decrement => (_) async {
_count--;
rebuild();
},
CounterEvent.reset => (_) async {
_count = 0;
rebuild();
},
};
}
}
Emit events from the UI:
TViewModelBuilder<CounterViewModel>(
viewModelBuilder: () => CounterViewModel(),
builder: (context, model, isInitialised, child) {
return Column(
children: [
Text('Count: ${model.count}'),
ElevatedButton(
onPressed: () => model.emit(CounterEvent.increment),
child: const Text('Increment'),
),
],
);
},
);
4. Managing Busy State #
The TBusyManagement mixin provides:
isBusy: AValueListenable<bool>for the busy state.setBusy(bool isBusy, {String? title, String? message}): Update the busy state.disposeBusyManagement(): Clean up resources (call indispose).
5. Handling Errors #
The TErrorManagement mixin provides:
hasError: AValueListenable<bool>for the error state.setError(bool hasError, {String? title, String? message}): Update the error state.disposeErrorManagement(): Clean up resources (call indispose).
6. Global Busy State with TBusyService #
For application-wide loading indicators:
void main() {
TBusyService.initialise(
busyMessageDefault: "Please wait...",
busyTypeDefault: TBusyType.indicatorBackdrop,
timeoutDurationDefault: const Duration(seconds: 30),
);
runApp(MyApp());
}
Use TBusyServiceManagement mixin in ViewModels to control the global busy state, or use TViewModelBuilder.minBusyDuration to show a busy overlay during initialisation.
7. Passing Arguments to ViewModel #
Pass arguments via argumentBuilder and access them in initialise():
TViewModelBuilder<MyViewModel>(
viewModelBuilder: () => MyViewModel(),
argumentBuilder: () => "Hello",
builder: (context, model, isInitialised, child) { ... },
);
The argument is available as arguments inside the ViewModel after initState.
Example Project #
Check the /example directory for a complete Flutter application demonstrating Turbo MVVM features.
Dependencies #
provider: Used internally byTViewModelBuilderfor efficient state propagation.
Contributing #
Contributions are welcome! Please open issues or pull requests on the GitHub repository.
License #
This package is licensed under the MIT License. See the LICENSE file for details.