live_states ๐Ÿš€

Stop watching, start living.
A surgical precision, high-performance MVVM framework for Flutter that eliminates boilerplate with Zone-based automatic dependency tracking.

Pub Version License


๐Ÿ“ธ Demo

LiveStates Demo


โœจ The "Aha!" Moment

๐Ÿช„ Implicit vs. Explicit Tracking

Stop manually listing what you want to watch. In other frameworks, missing a watch call means your UI stays stale. In live_states, if you touch it, we track it.

// โŒ Traditional (Riverpod/Provider)
// You must explicitly watch every single state. High mental overhead.
final name = ref.watch(nameProvider);
final age = ref.watch(ageProvider);
final score = ref.watch(scoreProvider);
return Text('$name ($age): $score');

// โœ… live_states (Automatic)
// Just access .value. The Zone-based tracker handles the magic.
LiveScope.free(
  builder: (context, _) => Text('${vm.name.value} (${vm.age.value}): ${vm.score.value}')
)

๐ŸŽฏ Surgical Precision with Zero Effort

Want to rebuild only one Text node without triggering a full page re-render? No need to extract widgets or use complex Selectors.

@override
Widget build(BuildContext context, UserVM viewModel) {
  return Scaffold(
    body: Column(
      children: [
        const HeavyStaticWidget(), // This stays static forever
        LiveScope.vm<UserVM>(
          builder: (c, vm, _) => Text('Live Score: ${vm.score.value}'), // Surgical update
        ),
      ],
    ),
  );
}

๐ŸŒŸ Why live_states?

Feature LiveStates Provider / Riverpod Bloc / Redux GetX
Tracking Automatic (Zone) Manual (watch) Manual (Streams) Proxy-based
Boilerplate Zero Medium High Low
Precision Surgical (Scope) Widget-level Widget-level Component-level
Lifecycle Deep Integration Explicit Independent Global/Manual
Architecture Pure MVVM Functional/DI Event-driven Variable

๐Ÿš€ Key Features

graph TD
    subgraph View_Layer
        W[LiveWidget] --> S[LiveScope]
    end

    subgraph Logic_Layer
        VM[LiveViewModel] --> LD[LiveData]
        VM --> LC[LiveCompute]
    end

    LD -- "1. Auto-track (Zone)" --> S
    LC -- "2. Derived & Filtered" --> S
    S -- "3. Surgical Rebuild" --> W
    
    Action(User_Action) --> VM
    VM -- "Update .value" --> LD
  • ๐Ÿช„ Magic Dependency Tracking: Leveraging Dart's Zone mechanism to automatically detect dependencies during the build process. No more manual listeners.
  • ๐ŸŽฏ Surgical Rebuilds: LiveScope allows you to isolate updates to the smallest possible Widget node, preventing unnecessary parent re-renders.
  • ๐Ÿ—๏ธ Pure MVVM Architecture: A clean separation of concerns. Your View talks to the ViewModel, and the ViewModel manages the State.
  • โ™ป๏ธ Deep Lifecycle Hooks: ViewModels that are actually aware of Flutter's lifecycle (init, dispose, activate, deactivate).
  • ๐Ÿงฌ Reactive Computing: LiveCompute handles complex derived states with built-in change verification to suppress redundant UI updates.
  • ๐Ÿ’พ State Persistence: Recoverable mixin allows your VM state to survive widget unmounting and app restarts effortlessly.

๐Ÿ“ฆ Getting Started

1. Define your ViewModel

class CounterVM extends LiveViewModel<CounterPage> {
  // Define reactive data
  late final counter = LiveData<int>(0, owner);

  // Derived state: only notifies if the BOOLEAN result changes!
  late final isEven = LiveCompute<bool>(owner, () => counter.value % 2 == 0);

  void increment() => counter.value++;
}

2. Build your View

class CounterPage extends LiveWidget {
  @override
  CounterVM createViewModel() => CounterVM();

  @override
  Widget build(BuildContext context, CounterVM viewModel) {
    return Scaffold(
      body: Center(
        child: LiveScope.vm<CounterVM>(
          builder: (context, vm, _) => Text('Count: ${vm.counter.value}'),
        ),
      ),
      floatingActionButton: FloatingActionButton(onPressed: viewModel.increment),
    );
  }
}

๐Ÿ› ๏ธ Advanced Tools

๐Ÿ” Surgical Precision with LiveScope

You can pass a child to LiveScope to prevent it from ever rebuilding, even when the scope itself refreshes.

LiveScope.vm<MyVM>(
  child: MyComplexStaticWidget(), // This is built once and reused
  builder: (context, vm, child) => Column(
    children: [
      Text(vm.data.value),
      child!, // Never rebuilds
    ],
  ),
)

๐Ÿ’พ State Recovery (Recoverable)

Keep your UI state alive even when the user navigates away and back.

class SearchVM extends LiveViewModel with Recoverable {
  @override
  String get storageKey => 'search_cache';
  
  @override
  Map<String, dynamic>? storage() => {'q': query.value};
  
  @override
  void recover(Map<String, dynamic>? s) => query.value = s?['q'] ?? '';
}

๐Ÿ”„ Cascaded Refresh (Refreshable)

Signal a top-down refresh for an entire tree of ViewModels (perfect for pull-to-refresh).

class RootVM extends LiveViewModel with Refreshable {
  @override
  Future<bool> onRefresh() async {
    await loadData();
    return true; // All child VMs using Refreshable will also be triggered
  }
}

๐Ÿงช Built for Reliability

live_states is covered by an extensive test suite ensuring memory safety, zero-leak ticker providers, and accurate dependency resolution.

Check the Example Project for a full implementation of search filtering and shopping cart logic.


๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

Libraries

live_states
LiveStates is a lightweight and high-performance state management framework.