view_model 0.15.0-dev.0 copy "view_model: ^0.15.0-dev.0" to clipboard
view_model: ^0.15.0-dev.0 copied to clipboard

Everything is ViewModel. Enjoy automatic lifecycle management, prevent memory leaks, and share state effortlessly. Simple, lightweight, and powerful.

view_model #

Lightweight Flutter state management that makes it simple.

Package Version
view_model Pub Version
view_model_annotation Pub Version
view_model_generator Pub Version

License: MIT

English | 简体中文

Why view_model? #

  • Minimal boilerplate - Just add a mixin, no root wrappers or complex setup
  • Automatic lifecycle - Auto cleanup when widgets dispose, prevents memory leaks
  • Smart performance - Auto-pause updates for background or hidden pages
  • Fine-grained reactivity - Field-level updates, rebuild only what changed
  • ViewModel dependencies - ViewModels can directly access and listen to each other

Quick Start #

Installation #

dependencies:
  view_model: ^0.14.2

Three Simple Steps #

1. Define State Class

class CounterState {
  final int count;

  const CounterState({this.count = 0});

  CounterState copyWith({int? count}) {
    return CounterState(count: count ?? this.count);
  }
}

2. Create ViewModel

import 'package:view_model/view_model.dart';

// Define Spec (global singleton)
final counterSpec = ViewModelSpec<CounterViewModel>(
  key: 'counter',  // Use key to share instance
  builder: () => CounterViewModel(),
);

// Create ViewModel
class CounterViewModel extends StateViewModel<CounterState> {
  CounterViewModel() : super(state: const CounterState());

  void increment() {
    setState(state.copyWith(count: state.count + 1));
  }

  void decrement() {
    setState(state.copyWith(count: state.count - 1));
  }
}

3. Use in Widget

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

// Add ViewModelStateMixin
class _CounterPageState extends State<CounterPage>
    with ViewModelStateMixin<CounterPage> {

  @override
  Widget build(BuildContext context) {
    // Use viewModelBinding.watch to listen to ViewModel
    final counter = viewModelBinding.watch(counterSpec);

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Count: ${counter.state.count}',
              style: const TextStyle(fontSize: 48)),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: counter.decrement,
                  child: const Text('-'),
                ),
                const SizedBox(width: 20),
                ElevatedButton(
                  onPressed: counter.increment,
                  child: const Text('+'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Initialization (Optional) #

void main() {
  ViewModel.initialize(
    config: ViewModelConfig(
      isLoggingEnabled: true,  // Enable logging
    ),
  );
  runApp(const MyApp());
}

Core Concepts #

ViewModelBinding - ViewModel Execution Framework #

ViewModelBinding provides four core methods to access ViewModels:

Method Usage Effect
viewModelBinding.watch(provider) In build method Listen to changes and rebuild widget
viewModelBinding.read(provider) In event callbacks Read data without listening
viewModelBinding.watchCached({key}) Access existing instance Listen to cached ViewModel
viewModelBinding.readCached({key}) Read existing instance Don't listen to cached ViewModel
// Example: watch vs read
@override
Widget build(BuildContext context) {
  final vm = viewModelBinding.watch(provider);  // ✅ Use watch in build
  return ElevatedButton(
    onPressed: () {
      final vm = viewModelBinding.read(provider);  // ✅ Use read in callbacks
      vm.doSomething();
    },
    child: Text(vm.state.title),
  );
}

Instance Sharing and Lifecycle #

1. Auto Cleanup (Default)

Without key, each widget has its own instance, auto-disposed when widget unmounts:

final provider = ViewModelSpec<MyViewModel>(
  builder: () => MyViewModel(),
  // No key, auto cleanup
);

2. Shared Instance

With key, widgets with same key share one instance, disposed when all widgets unmount:

final userSpec = ViewModelSpec<UserViewModel>(
  key: 'current-user',  // All widgets with this key share instance
  builder: () => UserViewModel(),
);

3. Keep Alive Forever

With aliveForever: true, instance never disposes:

final configSpec = ViewModelSpec<ConfigViewModel>(
  key: 'app-config',
  aliveForever: true,  // Never dispose
  builder: () => ConfigViewModel(),
);

Smart Pause Mechanism #

Built-in auto-pause to save performance:

  1. App Background Pause - Pause when app goes to background
  2. Route Overlay Pause - Pause when route is covered by another route
  3. TabBar Pause - Pause invisible tabs in TabBarView

Updates are queued while paused, triggered once when resumed to avoid wasted rebuilds.

Enable Route Pause

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorObservers: [ViewModel.routeObserver],  // Add route observer
      home: HomePage(),
    );
  }
}

Parameterized Specs #

Create and reuse instances based on parameters:

// Define provider with parameter
final userSpec = ViewModelSpec.arg<UserViewModel, int>(
  builder: (userId) => UserViewModel(userId),
  key: (userId) => 'user_$userId',  // Different params use different keys
);

// Usage
Widget build(BuildContext context) {
  final user1 = viewModelBinding.watch(userSpec(42));   // Create user_42
  final user2 = viewModelBinding.watch(userSpec(100));  // Create user_100
  final user3 = viewModelBinding.watch(userSpec(42));   // Reuse user_42
}

Supports 1-4 parameters: arg, arg2, arg3, arg4

ViewModel Dependencies #

ViewModels can directly access other ViewModels:

final authSpec = ViewModelSpec<AuthViewModel>(
  builder: () => AuthViewModel(),
);

class UserProfileViewModel extends StateViewModel<UserState> {

  void loadProfile() {
    // Read auth ViewModel
    final auth = viewModelBinding.read(authSpec);

    if (auth.isLoggedIn) {
      // Load user data...
    }
  }

  // Listen to other ViewModel changes
  @override
  void onCreate(InstanceArg arg) {
    super.onCreate(arg);

    listenState(authSpec, (previous, next) {
      if (next.isLoggedOut) {
        // Clear user data
        setState(const UserState());
      }
    });
  }
}

Fine-Grained Reactivity #

1. ValueWatcher - Field-Level Rebuild

Only listen to specific fields in state, reduce unnecessary rebuilds:

StateViewModelValueWatcher<UserViewModel, UserState>(
  stateViewModel: userViewModel,
  selectors: [
    (state) => state.name,  // Only listen to name
    (state) => state.age,   // Only listen to age
  ],
  builder: (state) => Text('${state.name}, ${state.age}'),
)

2. ObservableValue - Standalone Reactive Value

Create independent reactive values without ViewModels:

// Create shared reactive value
final counter = ObservableValue<int>(0, shareKey: 'counter');

// Modify anywhere
counter.value = 42;

// Listen in widget
ObserverBuilder<int>(
  observable: counter,
  builder: (value) => Text('$value'),
)

Lifecycle Hooks #

class MyViewModel extends StateViewModel<MyState> {
  @override
  void onCreate(InstanceArg arg) {
    super.onCreate(arg);
    // Initialize resources
    print('ViewModel created');
  }

  @override
  void onBindVef(InstanceArg arg, String vefId) {
    super.onBindVef(arg, vefId);
    // New widget started listening
    print('Widget bound');
  }

  @override
  void onUnbindVef(InstanceArg arg, String vefId) {
    super.onUnbindVef(arg, vefId);
    // Widget stopped listening
    print('Widget unbound');
  }

  @override
  void onDispose(InstanceArg arg) {
    // Cleanup resources
    print('ViewModel disposed');
    super.onDispose(arg);
  }
}

Code Generation #

Use @GenProvider annotation to auto-generate specs:

1. Add Dependencies #

dependencies:
  view_model: ^0.14.2
  view_model_annotation: ^0.14.2

dev_dependencies:
  view_model_generator: ^0.14.2
  build_runner: ^2.4.0

2. Use Annotation #

import 'package:view_model_annotation/view_model_annotation.dart';

part 'user_view_model.vm.dart';  // Generated file

@GenProvider(
  key: Expression('user_\$userId'),  // Supports string interpolation
  aliveForever: false,
)
class UserViewModel extends StateViewModel<UserState> {
  factory UserViewModel.provider(int userId) => UserViewModel(userId);

  UserViewModel(this.userId) : super(state: UserState());

  final int userId;
}

3. Run Generation #

dart run build_runner build

Generated code:

// user_view_model.vm.dart
final userSpec = ViewModelSpec.arg<UserViewModel, int>(
  builder: (userId) => UserViewModel(userId),
  key: (userId) => 'user_$userId',
);

Advanced Features #

Use in Plain Dart Classes #

Not limited to widgets, any Dart class can use it:

class StartupTask with ViewModelBinding {
  Future<void> run() async {
    final config = read(configSpec);
    await config.initialize();

    final auth = read(authSpec);
    await auth.checkLogin();
  }
}

final configSpec = ViewModelSpec<ConfigViewModel>(
  builder: () => ConfigViewModel(),
);

final authSpec = ViewModelSpec<AuthViewModel>(
  builder: () => AuthViewModel(),
);

// Use in main
void main() {
  ViewModel.initialize();
  StartupTask().run();
  runApp(MyApp());
}

Repository as ViewModel #

class UserRepository with ViewModel {

  Future<User> fetchUser(int id) async {
    // Can access other ViewModels
    final token = read(authSpec).token;
    return await api.getUser(id, token);
  }
}

final userRepoSpec = ViewModelSpec<UserRepository>(
  builder: () => UserRepository(),
);

Global Lifecycle Observation #

void main() {
  ViewModel.addLifecycle(MyObserver());
  runApp(MyApp());
}

class MyObserver implements ViewModelLifecycle {
  @override
  void onCreate<T extends ViewModel>(T vm, InstanceArg arg) {
    print('Created: ${vm.runtimeType}');
  }

  @override
  void onDispose<T extends ViewModel>(T vm, InstanceArg arg) {
    print('Disposed: ${vm.runtimeType}');
  }
}

Examples #

Check example directory:

  • counter - Simple counter showing basic usage
  • todo_list - TODO app showing complex state management

Documentation #

  • Architecture Guide
  • Pause and Resume Mechanism
  • ValueObserver Documentation
  • Code Generation Guide

License #

MIT License - See LICENSE file

Contributing #

Issues and Pull Requests are welcome!

Report issues at: https://github.com/lwj1994/flutter_view_model/issues

11
likes
0
points
1.76k
downloads

Publisher

verified publisherpub.lwjlol.com

Weekly Downloads

Everything is ViewModel. Enjoy automatic lifecycle management, prevent memory leaks, and share state effortlessly. Simple, lightweight, and powerful.

Repository (GitHub)
View/report issues

Topics

#state-management #view-model #caching #dependency-injection #vm

License

unknown (license)

Dependencies

flutter, meta, stack_trace, view_model_annotation

More

Packages that depend on view_model