MobX for Dart and Flutter

website·Indexed 30 days ago

https://mobx.netlify.app

A reactive state management library for Dart and Flutter based on the triad of Observables, Actions, and Reactions. It provides tools like the Observer widget, ReactiveContext, and specialized observable types for Lists, Maps, and Streams to synchronize application state with the UI.

Tokens
18.5K
Snippets
96
Records
118
Agent score
74%

What's inside MobX

  1. Overview of MobX for Dart and Flutter

    MobX is a state-management library for Dart and Flutter that automatically connects reactive data to the UI. It is based on three core concepts: Observables (the state), Actions (methods that mutate state), and Reactions (components that respond to state changes). It supports Null Safety, Dart 3.0, and works across iOS, Android, Web, MacOS, Linux, and Windows.
  2. Understand MobX Reactivity Fundamentals

    MobX operates on a Producer-Consumer relationship. Observables act as producers that notify of changes, while reactions (such as autorun, reaction, and when) act as consumers that respond to those changes. Reactivity is established automatically when an observable is read inside a reaction's tracking function; this links the reaction to the observable without manual wiring.
  3. Publish MobX packages via GitHub Actions

    Once code is formatted, versions are updated in pubspec.yaml and exposed via Melos, and the changelog is updated, push the changes to Git. GitHub Actions will automatically:

    1. Compare local and remote versions.
    2. Publish to pub.dev if a newer version is detected.
    3. Tag the commit using the format <package-name>-<version-number>.
  4. Use ObservableList for reactive list mutations

    An ObservableList tracks when items are added, removed, or modified and notifies observers. To ensure an Observer tracks mutations when passing the list to a child widget, call .toList() on the ObservableList. This converts it to a standard List for the child while maintaining the observation link in the parent.
    class Controller {
      final ObservableList<String> observableList = ObservableList<String>();
    }
    
    // In the UI
    Observer(builder: (_) {
      return ChildWidget(
        list: controller.observableList.toList(), // MobX detects mutations here
      );
    }),
    
    class ChildWidget extends StatelessWidget {
      final List<String> list; // Use standard List, not ObservableList, in child widgets
      const ChildWidget({super.key, required this.list});
    
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          itemCount: list.length,
          itemBuilder: (context, index) => ListTile(title: Text(list[index])),
        );
      }
    }
  5. Make a Dart Stream reactive using ObservableStream

    The ObservableStream<T> class in MobX wraps a standard Dart Stream<T> and makes it reactive. When the underlying stream emits a new value, any reaction (such as an Observer widget) that reads the .value property of the ObservableStream will automatically re-execute.
    // Inside a MobX Store
    late final StreamController<int> _streamController;
    late final ObservableStream<int?> randomStream;
    
    void init() {
      _streamController = StreamController<int>();
      // Wrap the stream to make it reactive
      randomStream = ObservableStream(_streamController.stream);
    }
  6. Manage MobX stores using the provider package

    Use the provider package to create a repository of stores at the App level, avoiding singletons. Stores are initialized using MultiProvider and can be retrieved within a widget's build method using Provider.of<T>(context) or the Consumer<T> widget.
    // 1. Setup stores at the app level
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
            Provider<MultiCounterStore>(create: (_) => MultiCounterStore())
          ],
          child: MaterialApp(
            home: ExampleList(),
          ),
        );
      }
    }
    
    // 2. Retrieve the store in a nested widget
    class CounterListPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final store = Provider.of<MultiCounterStore>(context);
    
        return Observer(
            builder: (_) => Column(children: <Widget>[
                  RaisedButton(
                    onPressed: store.addCounter,
                    child: const Text('Add Counter'),
                  ),
                ]));
      }
    }
  7. Implement asynchronous form validation with ObservableFuture

    For validations requiring server-side checks (e.g., checking if a username is taken), use an ObservableFuture. This allows the UI to observe the status of the async call and provide visual feedback while the validation is in progress.
    abstract class _FormStore with Store {
      // ...
    
      @observable
      ObservableFuture<bool> _usernameCheck = ObservableFuture.value(true);
    
      @action
      Future validateUsername(String value) async {
        if (value == null || value.isEmpty) {
          error.username = 'Cannot be blank';
          return;
        }
    
        try {
          // Wrap the future to track the status of the call
          _usernameCheck = ObservableFuture(checkValidUsername(value));
          error.username = null;
    
          final isValid = await _usernameCheck;
          if (!isValid) {
            error.username = 'Username cannot be "admin"';
            return;
          }
        } catch (_) {
          error.username = null;
        }
    
        error.username = null;
      }
    }
  8. Update package versions in pubspec.yaml

    When releasing a new version for MobX packages, update the version field in the pubspec.yaml file following the semantic versioning policy. For example, when moving from version 0.1.0 to 0.2.0, update the field accordingly.
    version: 0.2.0
  9. Implement Reactions for side-effects

    Reactions are derivations that produce no output value and are used exclusively to trigger side-effects. Key implementation requirements include:

    • Scheduling: Use a sync-scheduler or a timer-based debounced scheduler.
    • Error Handling: Exceptions must be handled and reported via global handlers.
    • Lifecycle: Must be disposable.
    • Convergence: Implement a check for convergence with a limit of 100 iterations.
  10. Handle asynchronous mutations with @action

    Action methods can be asynchronous. The MobX code-generator ensures that all mutations interspersed with async calls are wrapped within actions using zones.
      @action
      Future<List<Repository>> fetchRepos() async {
        repositories = [];
        final future = client.repositories.listUserRepositories(user).toList();
        fetchReposFuture = ObservableFuture(future);
    
        return repositories = await future;
      }
  11. Install MobX and Flutter MobX packages

    To use MobX with Flutter, you need the core MobX library for state management, flutter_mobx for the Observer widget, and mobx_codegen with build_runner to generate the necessary boilerplate code via annotations.
    dependencies:
      mobx: ^2.6.1
      flutter_mobx: ^2.3.0
    
    dev_dependencies:
      build_runner: ^2.16.1
      mobx_codegen: ^2.7.8
  12. Connect a MobX Store to a Flutter Widget using Observer

    To make a Flutter widget react to changes in a MobX store, wrap the specific part of the UI that depends on the observable state in an Observer widget. The Observer automatically tracks any observable values read within its builder function and triggers a re-render when those values change. This allows the use of StatelessWidget even for dynamic state.
    import 'package:flutter/material.dart';
    import 'package:flutter_mobx/flutter_mobx.dart';
    import 'counter.dart';
    
    final counter = Counter(); // Instantiate the store
    
    // Inside a Widget's build method:
    Observer(
      builder: (_) => Text(
        '${counter.value}',
        style: Theme.of(context).textTheme.headline4,
      ),
    )