zenui 0.1.0 copy "zenui: ^0.1.0" to clipboard
zenui: ^0.1.0 copied to clipboard

Fine-grained reactive UI for Flutter — aspect-level rebuilds powered by ZenSignals.

ZenUI #

Dart Flutter License: MIT

Fine-grained reactive UI for Flutter.

ZenUI is the reactive UI layer of ZenSuite: Flutter widgets and context that expose one signal-backed computed per UI concern, so your tree rebuilds at the granularity you choose — not at the granularity InheritedWidget gives you for free.

Built on ZenSignals (SignalBuilder, ComputedNotifier, batch). Today the package ships ReactiveMediaQuery for aspect-level MediaQueryData; more fine-grained UI primitives will follow the same model.


Table of contents #


Fine-grained reactive UI #

Layer Granularity Example
Widget tree Often whole subtrees MediaQuery.of(context) when any field changes
Flutter aspect APIs One MediaQueryData field MediaQuery.paddingOf(context) — still InheritedWidget rebuilds
ZenUI One computed per aspect (and derivations you define) ReactiveMediaQuery.paddingOf(context)(); or computed((_) => padding().bottom) for a single edge

ZenUI’s contract:

  1. Push, not pull — subscribe to ComputedNotifiers; let ZenSignals decide what re-runs.
  2. Aspect-first — each public field of layout/system UI gets its own reactive channel.
  3. Interop — keep standard Flutter widgets working (MediaQuery, Material, Cupertino) while opt-in code goes fine-grained.
Typical Flutter UI ZenUI
Rebuild unit Element / InheritedWidget Signal dependency
MediaQuery read paddingOf(context)EdgeInsets paddingOf(context)()EdgeInsets, tracks only padding
Custom precision Manual Listenable / setState createComputed on top of aspect computeds
Overrides Nested MediaQuery ReactiveMediaQueryOverride + root merge

Components #

Fine-grained reactive UI primitives in this package:

Component Description
ReactiveMediaQuery Root widget: observes the FlutterView, syncs each MediaQueryData aspect into signals, and inserts a standard MediaQuery for interoperability.
ReactiveMediaQueryOverride Composable overrides above or below the root — ancestor scopes merge at resolution; descendant scopes create a nested fine-grained subtree.
ReactiveMediaQueryData Read-only bag of aspect-specific ComputedNotifiers (size, padding, viewInsets, …).
ReactiveMediaQueryScope InheritedWidget locator for ReactiveMediaQueryData (notifications come from signals, not updateShouldNotify).

More components will be added here as the library grows; each follows the same fine-grained reactive pattern.


Getting started #

Add the dependency:

dependencies:
  zenui: ^0.0.1
  zensignals: ^1.0.0

Import ZenUI (and ZenSignals for SignalBuilder / createComputed):

import 'package:zenui/zenui.dart';
import 'package:zensignals/zensignals.dart';

Wrap the subtree that should share fine-grained layout metrics:

runApp(
  ReactiveMediaQuery(
    child: MaterialApp(
      home: MyHomePage(),
    ),
  ),
);

ReactiveMediaQuery #

The first ZenUI primitive: fine-grained MediaQuery for Flutter.

Setup #

Place ReactiveMediaQuery near the root of the widget tree that should share view metrics. It:

  1. Resolves the current FlutterView (or uses an explicit view).
  2. Builds MediaQueryData.fromView, merging ancestor MediaQuery platform data when appropriate.
  3. Applies any ReactiveMediaQueryOverride ancestor scopes, then optional dataOverrides on the root.
  4. Publishes ReactiveMediaQueryScope + a standard MediaQuery for descendants.
ReactiveMediaQuery(
  dataOverrides: (data) => data.copyWith(
    padding: const EdgeInsets.only(bottom: 34),
  ),
  child: MaterialApp(home: HomePage()),
)
Parameter Purpose
view Explicit view; defaults to View.maybeOf or the implicit view.
ignoreParentData When true, ignores platform fields from an ancestor MediaQuery (mirrors fromView behavior).
dataOverrides MediaQueryData Function(MediaQueryData) applied on the root after view + ancestor overrides.

Reading values #

Use the static *Of(context) helpers. Each returns a ComputedNotifier<T> — read with call syntax inside reactive UI (SignalBuilder):

SignalBuilder(
  forceRebuild: false,
  builder: (context) {
    final width = ReactiveMediaQuery.widthOf(context)();
    final padding = ReactiveMediaQuery.paddingOf(context)();
    return Text('${width.toStringAsFixed(0)} · bottom ${padding.bottom}');
  },
);
Syntax Fine-grained?
ReactiveMediaQuery.paddingOf(context)() Yes — padding aspect only
ReactiveMediaQuery.of(context).padding() Yes — same computed
.value outside a reactive read No — snapshot (see ZenSignals)

maybeOf / maybePaddingOf / … return null when no scope is present.

Fine-grained subscriptions #

The padding aspect still rebuilds when any edge changes. For finer precision, derive a nested computed:

class _BottomInsetState extends State<BottomBar>
    with ReactiveNotifierMixin<BottomBar> {
  late final _bottom = createComputed(
    (_) => ReactiveMediaQuery.paddingOf(context)().bottom,
    listen: false,
  );

  @override
  Widget build(BuildContext context) {
    return SignalBuilder(
      forceRebuild: false,
      builder: (_) => Padding(
        padding: EdgeInsets.only(bottom: _bottom()),
        child: const TextField(),
      ),
    );
  }
}

padding.top noise no longer rebuilds this subtree; padding.bottom changes still do. The same pattern applies to viewInsets, size / width / height, devicePixelRatio, and other aspects.

Overrides #

ReactiveMediaQueryOverride keeps overrides composable without giving up fine-grained reactivity.

Above the root — ancestor scope merged at resolution (nearest wins on conflicts):

ReactiveMediaQueryOverride(
  dataOverrides: (d) => d.copyWith(size: const Size(390, 844)),
  child: ReactiveMediaQuery(child: app),
)

Below the root — nested fine-grained scope for a subtree only:

ReactiveMediaQuery(
  child: ReactiveMediaQueryOverride(
    dataOverrides: (d) => d.copyWith(
      padding: const EdgeInsets.only(bottom: 80),
    ),
    child: bottomBar,
  ),
)

Root dataOverrides run after all ancestor override scopes.


API reference #

ReactiveMediaQuery #

Member Description
ReactiveMediaQuery(...) Root observer; fine-grained aspect computeds.
ReactiveMediaQuery.of(context) Nearest ReactiveMediaQueryData (throws if missing).
ReactiveMediaQuery.maybeOf(context) Same, or null.
sizeOf, widthOf, heightOf, paddingOf, viewInsetsOf, viewPaddingOf, … Per-aspect ComputedNotifiers (each has maybe*).

ReactiveMediaQueryOverride #

Member Description
ReactiveMediaQueryOverride({ dataOverrides, child }) Ancestor or descendant overrides (see Overrides).

ReactiveMediaQueryData #

One ComputedNotifier per MediaQueryData field. Batched updates when resolved data changes.

ReactiveMediaQueryDataOverrides #

typedef ReactiveMediaQueryDataOverrides =
    MediaQueryData Function(MediaQueryData data);

Example #

Runnable demo of fine-grained vs broad MediaQuery reads: example/.

cd packages/zenui/example && flutter run

See example/README.md for panel descriptions.


ZenSuite integration #

Package Role
ZenSignals Fine-grained reactive state
ZenUI (this package) Fine-grained reactive UI
ZenBus High-performance event bus
ZenQuery Async data fetching and mutations

ZenSignals owns signals and computeds; ZenUI applies that model to Flutter layout and system UI:

ReactiveMediaQuery(
  child: SignalBuilder(
    builder: (context) {
      final bottom = ReactiveMediaQuery.paddingOf(context)().bottom;
      return Padding(
        padding: EdgeInsets.only(bottom: bottom),
        child: child,
      );
    },
  ),
);

License #

MIT © Bui Dai Duong

0
likes
160
points
9
downloads

Documentation

API reference

Publisher

verified publisherzennn.dev

Weekly Downloads

Fine-grained reactive UI for Flutter — aspect-level rebuilds powered by ZenSignals.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, zensignals

More

Packages that depend on zenui