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

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

ViewModel Logo

✨ view_model: Lightweight Flutter State Management #

Ultra-lightweight (just with) | Zero intrusion | Say goodbye to BuildContext hell

Only ~6K lines of code, yet transforms your architecture completely 🚀

Package Version
view_model Pub Version
view_model_annotation Pub Version
view_model_generator Pub Version

Codecov

ChangeLog | 中文文档 | Architecture Guide


💡 Why view_model? #

✨ Three Core Strengths #

🪶 Ultra-Lightweight = Zero Overhead

  • Minimal footprint: Only ~6K lines of code, 3 dependencies (flutter + meta + stack_trace)
  • Zero setup: No root widget wrapping, no mandatory initialization
  • On-demand creation: ViewModels are created lazily and disposed automatically

🎯 Minimal Intrusion = Maximum Compatibility

  • Just with: Add with ViewModelStateMixin to your State—that's it
  • Drop-in ready: Works with any existing Flutter code, integrate anytime
  • Pure Dart mixins: Leverages Dart 3 mixin capabilities, zero inheritance pollution

🌈 Complete Flexibility

  • Access anywhere: Use ViewModels in Widgets, Repositories, Services—no BuildContext needed
  • Automatic memory management: Reference counting + auto-disposal means no memory leaks
  • Share or isolate: Need a singleton? Add a key. Need isolation? Don't. It's that simple.

📦 Installation #

dependencies:
  view_model: ^latest_version

dev_dependencies:
  build_runner: ^latest_version
  view_model_generator: ^latest_version  # Highly recommended for less boilerplate!

🚀 Get Started in 3 Steps #

Step 1️⃣: Write Your Business Logic #

Just use with ViewModel (yes, it's that simple):

class CounterViewModel with ViewModel {
  int count = 0;

  void increment() {
    update(() => count++);  // Automatically notifies UI
  }
}

Why with instead of extends? Dart mixins enable composition over inheritance—more flexible and keeps your class hierarchy clean!


Step 2️⃣: Register a Provider #

final counterProvider = ViewModelProvider<CounterViewModel>(
  builder: () => CounterViewModel(),
);

Pro tip: Skip this step entirely by using view_model_generator—just add an annotation and it's auto-generated! 🎉


Step 3️⃣: Use in Your Widget #

Add one mixin, unlock superpowers:

class CounterPage extends StatefulWidget {
  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage>
    with ViewModelStateMixin {  // 👈 Just this one line!

  @override
  Widget build(BuildContext context) {
    final vm = vef.watch(counterProvider);  // Automatically listens

    return Scaffold(
      body: Center(child: Text('${vm.count}')),
      floatingActionButton: FloatingActionButton(
        onPressed: vm.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

Intrusion comparison:

Solution Changes Required Root Wrapping BuildContext Dependency
view_model ✅ Just add mixin ❌ No ❌ No
Provider ⚠️ InheritedWidget ✅ Yes ✅ Yes
Riverpod ⚠️ ConsumerWidget ✅ Yes ❌ No
GetX ⚠️ Often global state ❌ No ❌ No

🛠️ Core Features #

1️⃣ Universal Access with Vef (Custom Ref) #

What is vef? Vef = ViewModel Execution Framework. It's a mixin you can add to any class, giving it the power to access ViewModels anywhere!

💡 Fun fact: ViewModelStateMixin is actually powered by WidgetVef under the hood—a Flutter-optimized variant of Vef. This ensures a consistent API whether you're in Widgets, ViewModels, or pure Dart classes!

class StartupTaskRunner with Vef {
  Future<void> run() async {
    final authVM = vef.read(authProvider);
    await authVM.checkAuth();

    final configVM = vef.read(configProvider);
    await configVM.fetchRemoteConfig();

    
  }

  @override
  void dispose() {
    super.dispose(); 
  }
}

#### 📱 In Widgets (Built-in)

```dart
class _MyPageState extends State<MyPage> with ViewModelStateMixin {
  @override
  Widget build(BuildContext context) {
    final vm = vef.watch(myProvider);  // Auto-reactive
    return Text(vm.data);
  }
}

🧠 In ViewModels (Built-in)

ViewModels can coordinate with each other:

class CartViewModel with ViewModel {
  void checkout() {
    final userVM = vef.read(userProvider);  // Direct access to other VMs
    processOrder(userVM.user);
  }
}

🏗️ In Any Class (Custom Ref)

Need a pure logic manager? Just with Vef:

class StartupTaskRunner with Vef {
  Future<void> run() async {
    final authVM = vef.read(authProvider);
    await authVM.checkAuth();

    final configVM = vef.read(configProvider);
    await configVM.fetchRemoteConfig();
  }

  @override
  void dispose() {
    super.dispose();  // Auto-cleans all dependencies
  }
}

🎯 Quick Reference: Vef Methods

Method Behavior Best For
vef.watch(provider) Reactive Inside build()—rebuilds on change
vef.read(provider) Direct access Callbacks, event handlers, or other ViewModels
vef.listen(provider) Side effects Navigation, snackbars, etc.
vef.watchCached(key:) Targeted Access specific shared instance by key
vef.readCached(key:) Targeted Read specific shared instance without listening
vef.listenState(provider) State Listener Listen to state changes (previous, current)
vef.listenStateSelect(provider) Selector Listen to specific state property changes

Legacy API support: Prefer the classic watchViewModel syntax? Go ahead! It's fully supported and powered by the high-performance vef engine under the hood:

Legacy Method Modern Equivalent Description
watchViewModel vef.watch Watch + auto-rebuild
readViewModel vef.read Direct read, zero overhead
listenViewModel vef.listen Listen without rebuild
watchCachedViewModel vef.watchCached Watch cached instance
readCachedViewModel vef.readCached Read cached instance
listenViewModelState vef.listenState Listen to state changes
listenViewModelStateSelect vef.listenStateSelect Listen to selected state changes

2️⃣ Immutable State (StateViewModel) #

For developers who love clean, immutable state! Pairs beautifully with Freezed

class UserViewModel extends StateViewModel<UserState> {
  UserViewModel() : super(state: UserState());

  void loadUser() async {
    setState(state.copyWith(isLoading: true));
    // ... fetch data ...
    setState(state.copyWith(isLoading: false, name: 'Alice'));
  }
}

3️⃣ Dependency Injection (Arguments) #

Real talk: Many Flutter "DI" libraries are actually Service Locators in disguise. True DI requires reflection or powerful meta-programming, but Flutter disables reflection.

We chose to embrace reality—use a clean, explicit argument system:

final userProvider = ViewModelProvider.arg<UserViewModel, int>(
  builder: (int id) => UserViewModel(id),
);

// Usage:
final vm = vef.watch(userProvider(42));

Simple, direct, debuggable. No magic tricks.


4️⃣ Instance Sharing (Keys) #

  • Isolated by default: Each widget gets its own ViewModel instance
  • Shared instances: Add a key, and widgets with the same key share the same instance
final productProvider = ViewModelProvider.arg<ProductViewModel, String>(
  builder: (id) => ProductViewModel(id),
  key: (id) => 'prod_$id',  // Same ID = shared instance
);

5️⃣ Automatic Lifecycle ♻️ #

Set it and forget it—memory management handled automatically:

  1. Creation: Auto-created on first watch or read
  2. Alive: Stays alive as long as any widget is using it
  3. Disposal: Auto-cleaned when the last user unmounts

Need a global singleton? Add aliveForever: true, perfect for Auth, App Config, etc:

final authProvider = ViewModelProvider(
  builder: () => AuthViewModel(),
  aliveForever: true,  // Never disposed
);

🏗️ Architecture Patterns #

In real-world apps, Repositories and Services can use with ViewModel to coordinate with other ViewModels—no BuildContext passing needed:

class UserRepository with ViewModel {
  Future<User> fetchUser() async {
    final token = vef.read(authProvider).token;  // Direct access
    return api.getUser(token);
  }
}

For detailed patterns, check out our Architecture Guide


🧪 Testing Made Easy #

Mocking is straightforward—no simulator required:

testWidgets('Displays correct user data', (tester) async {
  final mockVM = MockUserViewModel();
  userProvider.setProxy(ViewModelProvider(builder: () => mockVM));

  await tester.pumpWidget(MyApp());
  expect(find.text('Alice'), findsOneWidget);
});

⚙️ Global Configuration #

Configure in main():

void main() {
  ViewModel.initialize(
    config: ViewModelConfig(
      isLoggingEnabled: true,
      onListenerError: (error, stack, context) {
         // Report to Crashlytics
      },
    ),
  );
  runApp(MyApp());
}

📊 The Numbers Don't Lie #

Metric Value
Core codebase ~6K lines (with comments)
Required dependencies 3 (flutter, meta, stack_trace)
Mixins needed 1 (ViewModelStateMixin)
Root widget wrapping ❌ None
Mandatory initialization ❌ Optional
Performance overhead Minimal (reference counting + Zone)

📜 License #

MIT License—use it freely! 💖


🎉 Bottom Line #

Tired of:

  • ❌ Passing BuildContext everywhere
  • ❌ Complex global state management
  • ❌ Memory leaks
  • ❌ Invasive code changes

Try view_model! Lightweight, clean, elegant—it'll transform how you build Flutter apps ✨

Remember: Just with, and everything becomes simple!


Built with ❤️ for the Flutter community.

11
likes
0
points
1.66k
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