all_observer 1.4.0 copy "all_observer: ^1.4.0" to clipboard
all_observer: ^1.4.0 copied to clipboard

Lightweight reactive state management for Flutter with zero external dependencies: observable values plus an auto-tracking Observer widget.

all_observer #

πŸ‡§πŸ‡· PortuguΓͺs | πŸ‡ΊπŸ‡Έ English

pub package license: MIT pub points 225 tests

Reactive state for Flutter with zero dependencies β€” final count = 0.obs; + Observer(...) and you're done.

all_observer hero

Table of contents #

Features #

  • πŸͺΆ Zero dependencies β€” the whole reactive core is built on Dart/Flutter alone, nothing else to keep in sync with your Flutter version.
  • βœ‚οΈ No boilerplate, no code generation β€” final count = 0.obs; plus Observer(() => ...) is a complete, working reactive pair.
  • 🎯 Granular rebuilds β€” dependencies are discovered by reading .value during a build, so only the widget that actually reads a value rebuilds.
  • πŸ›‘οΈ Safe by default β€” glitch-free diamond dependencies, race-safe async, unmounted-widget guards, and friendly warnings instead of crashes (with opt-in strictMode for CI).
  • πŸ§ͺ Testable by design β€” Observable/Computed are plain Dart objects, no wrapper/DI required to test them; see Testing.
  • πŸ”Œ ValueListenable interop β€” Observable<T> is a ValueListenable<T>, so it drops straight into ValueListenableBuilder, AnimatedBuilder, Listenable.merge.
  • 🩺 Built-in colored debug logging β€” flip ObserverConfig.logging = true and watch every create/update/track/dispose event in your terminal.

Installing #

flutter pub add all_observer
dependencies:
  all_observer: ^1.4.0
import 'package:all_observer/all_observer.dart';

Counter app step by step #

Step 1 β€” Create an observable #

final count = 0.obs; // ObservableInt

.obs wraps any value in an Observable β€” count now holds 0 and can be watched for changes.

Step 2 β€” Wrap your UI in an Observer #

import 'package:flutter/material.dart';
import 'package:all_observer/all_observer.dart';

final count = 0.obs;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Observer(() => Text('${count.value}')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => count.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Run it inside any MaterialApp(home: CounterPage()).

Step 3 β€” Update the value #

onPressed: () => count.value++,

Only the Observer(() => Text('${count.value}')) above rebuilds β€” nothing else in CounterPage re-renders, because it's the only widget that read count.value during its build.

Step 4 β€” Watch it happen #

ObserverConfig.logging = true;
[all_observer] ✚ Observable<int>(count) created β†’ 0
[all_observer] πŸ‘ Observer(unnamed) tracking: [count]
[all_observer] ↻ Observable<int>(count): 0 β†’ 1

The building blocks #

Observable #

Any value wrapped with .obs (or Observable<T>(initial) for custom types). Reading .value inside a tracked builder registers a dependency; writing only notifies when the new value differs.

final name = Observable<User?>(null, name: 'user');
name.value = User('Carlos');

More about Observable here.

Observer #

Auto-tracking widget: dependencies are re-discovered on every build, so conditional reads work out of the box.

Observer(() => Text('${count.value}'));

More about Observer here.

watch(context) β€” no wrapper needed #

Any widget can subscribe its own element directly from build(); only that widget rebuilds when the value changes.

@override
Widget build(BuildContext context) => Text('${count.watch(context)}');

More about watch(context) (including its lazy-cleanup trade-off) here.

Computed #

Lazy, memoized derived value, built on the same tracker Observer uses.

final fullName = Computed(() => '${firstName.value} ${lastName.value}');
Observer(() => Text(fullName.value)); // recomputes only when needed

More about Computed here.

Reactive collections #

ObservableList/ObservableMap/ObservableSet behave like their built-in counterparts, notifying at most once per mutating call.

final items = <String>[].obs;
Observer(() => Text('${items.length} items'));
items.addAll(['one', 'two']); // notifies once, not twice

More about collections here.

ObservableFuture (async state) #

Runs a Future and tracks its loading/data/error lifecycle, with race-safe refreshes.

final userFuture = ObservableFuture<User>(() => api.fetchUser(id));
Observer(() => userFuture.value.when(
  loading: (previousData) => const CircularProgressIndicator(),
  data: (user) => Text(user.name),
  error: (error, stackTrace) => Text('Error: $error'),
));

More about async state here.

Workers #

ever, once, debounce, interval β€” side effects driven by an observable change, without hand-rolled addListener calls.

final query = ''.obs;
final search = debounce(query, (String value) => runSearch(value),
    time: const Duration(milliseconds: 400));

More about workers here.

batch #

Coalesces multiple writes so manual (listen/ever) subscribers notify once instead of once per write β€” Observer already coalesces rebuilds per frame on its own.

More about batch and diamond dependencies here.

Observer vs watch(context) β€” choosing the right one #

Both use the exact same dependency tracker and re-discover dependencies on every build β€” neither is "smarter" than the other. The difference is where the subscription lives and how much code you write.

Quick-decision table #

Situation Reach for
Small leaf widget whose entire build() is reactive watch(context)
One reactive slice inside a large build() (e.g. only a Text in a Scaffold) Observer(() => ...)
Expensive static subtree that should never rebuild Observer.withChild(builder:, child:)
Long-lived global observable read by many short-lived screens Observer (eager dispose())
Reactive value inside an Observer builder watch(context) β€” delegates to the enclosing context, no double subscription

Side-by-side #

// watch(context) β€” the widget subscribes itself, no wrapper
class CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Text('${count.watch(context)}');
}

// Observer β€” a wrapper widget owns the subscription
Observer(() => Text('${count.value}'));

Both rebuild only the widget that read the value. The practical difference shows up when the build() is large:

// watch rebuilds the whole Scaffold when count changes
Widget build(BuildContext context) {
  final n = count.watch(context); // <-- subscribes this Element
  return Scaffold(
    body: Center(child: Text('$n')),
    // ... 50 other widgets
  );
}

// Observer rebuilds only the Text node
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Observer(() => Text('${count.value}')), // <-- only this rebuilds
    ),
    // ... 50 other widgets
  );
}

Cleanup behaviour #

Cleanup timing
Observer Immediate β€” dispose() unsubscribes on unmount
watch(context) Lazy β€” the dead subscription is released on the first notification after unmount (guaranteed no-op: nothing rebuilds, nothing throws). On an observable that never changes again the inert listener persists β€” prefer Observer for long-lived globals.

The rule of thumb #

Leaf widget, whole build() is reactive β†’ watch(context).
Reactive slice inside a big build(), static subtree, or eager cleanup β†’ Observer.

They are additive and compose freely. A watch call inside an Observer builder simply reads the value and delegates to the enclosing tracker β€” no extra subscription is created.

Comparison #

all_observer GetX Riverpod Bloc MobX signals
External dependencies Zero Zero (all-in-one) riverpod (+ generator, common) bloc, flutter_bloc mobx, build_runner Zero
Code generation None None Optional, common in practice None Required (@observable/@action) None
Boilerplate Minimal (.obs + Observer) Minimal Provider declarations Events/states/handlers Annotated store classes Minimal
Rebuild granularity Per-read, auto-tracked Per-read, auto-tracked Per ref.watch Per BlocBuilder/selector Per-read, auto-tracked Per-read, auto-tracked
Learning curve Low Low–medium Medium Medium–high Medium Low
Scope Reactivity only Full framework (state+routing+DI) State + DI graph State machine/event architecture Reactivity + actions Reactivity only

all_observer intentionally doesn't do routing, DI, or snackbars β€” that's a design choice, not a gap. Full, detailed comparison here.

When to use it (and when not to) #

Reach for all_observer when you want reactive state β€” counters, form fields, loading flags, a reactive list, a computed summary β€” without adopting a full architecture, and you want it composable with whatever DI/routing you already use.

Reach for something else when you specifically need what it specializes in: a compile-time-checked DI graph (Riverpod), an all-in-one framework with routing and DI (GetX), or an auditable event/state architecture for a large team (Bloc). all_observer has no opinion on where state lives, only on how it notifies, so it composes with any of them.

Documentation #

  • Core concepts β€” Observable, Observer, tracking, Computed.
  • Collections β€” ObservableList/Map/Set.
  • Async β€” ObservableFuture, ObservableStream, AsyncState.
  • Workers β€” ever, once, debounce, interval.
  • Advanced β€” batch, diamond dependencies, equals, setValue, strictMode, logging, design decisions, limitations.
  • Testing β€” how to test widgets and controllers that use all_observer, with real examples from the example app.
  • Comparison β€” detailed comparison vs GetX, Riverpod, Bloc, MobX, signals.
  • Migrating from GetX.
  • FAQ β€” troubleshooting and common questions.

Other packages by us #

all_observer is part of a small family of zero/low-dependency Dart & Flutter packages published under the opensource.tatamemaster.com.br verified publisher:

Package Version Description
all_validations_br pub Brazilian document validation (CPF, CNPJ, CNH, PIX), input formatters/masks, JWT/UUID/currency/encryption utilities.
all_box pub Synchronous key-value storage with crash-safe writes and a pure-Flutter reactive layer.
all_image_compress pub Pure-Dart image compression (JPEG, PNG, GIF, BMP, TIFF, WebP), running in isolates.

πŸ‘₯ Contributors #

Contributors

Made with contrib.rocks.

How to contribute #

Contributions are welcome! Read CONTRIBUTING.md to get started.


Issues and pull requests are welcome at the GitHub repository. Licensed under MIT.

3
likes
0
points
1.12k
downloads

Publisher

verified publisheropensource.tatamemaster.com.br

Weekly Downloads

Lightweight reactive state management for Flutter with zero external dependencies: observable values plus an auto-tracking Observer widget.

Repository (GitHub)
View/report issues

Topics

#state-management #reactive #observable

License

unknown (license)

Dependencies

flutter

More

Packages that depend on all_observer