oref 2.8.0 copy "oref: ^2.8.0" to clipboard
oref: ^2.8.0 copied to clipboard

A high-performance Flutter state management tool, Oref is one of the fastest Flutter signals and state management solutions.

example/lib/main.dart

import 'dart:math' as math;

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

void main() {
  runApp(const ExampleApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Oref Examples',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
        useMaterial3: true,
      ),
      home: const ExampleHome(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Oref Examples')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: const [
          _Section(
            title: 'Counter + computed + writableComputed',
            child: CounterSection(),
          ),
          SizedBox(height: 16),
          _Section(title: 'Effect + batch', child: EffectBatchSection()),
          SizedBox(height: 16),
          _Section(title: 'untrack()', child: UntrackSection()),
          SizedBox(height: 16),
          _Section(title: 'ReactiveMap', child: ReactiveMapSection()),
          SizedBox(height: 16),
          _Section(title: 'ReactiveSet', child: ReactiveSetSection()),
          SizedBox(height: 16),
          _Section(title: 'useAsyncData', child: AsyncDataSection()),
          SizedBox(height: 16),
          _Section(
            title: 'Walkthrough: searchable list',
            child: WalkthroughSection(),
          ),
          SizedBox(height: 16),
          _Section(
            title: 'Walkthrough: checkout total + autosave',
            child: CheckoutWorkflowSection(),
          ),
          SizedBox(height: 16),
          _Section(
            title: 'Walkthrough: form validation + async save',
            child: FormWorkflowSection(),
          ),
        ],
      ),
    );
  }
}

class _Section extends StatelessWidget {
  const _Section({required this.title, required this.child});

  final String title;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 0,
      color: Theme.of(context).colorScheme.surfaceContainerHighest,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(height: 12),
            child,
          ],
        ),
      ),
    );
  }
}

// #region counter-section
class CounterSection extends StatelessWidget {
  const CounterSection({super.key});

  @override
  Widget build(BuildContext context) {
    final count = signal<double>(context, 2);
    final doubled = computed<double>(context, (_) => count() * 2);
    final squared = writableComputed<double>(
      context,
      get: (_) => count() * count(),
      set: (value) {
        final safe = value < 0 ? 0.0 : value;
        count.set(math.sqrt(safe));
      },
    );

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('count: ${count().toStringAsFixed(1)}'),
        Text('doubled (computed): ${doubled().toStringAsFixed(1)}'),
        Text('squared (writable): ${squared().toStringAsFixed(1)}'),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children: [
            ElevatedButton(
              onPressed: () => count.set(count() + 1),
              child: const Text('Increment'),
            ),
            OutlinedButton(
              onPressed: () => squared.set(81),
              child: const Text('Set squared = 81'),
            ),
          ],
        ),
      ],
    );
  }
}
// #endregion counter-section

// #region effect-batch-section
class EffectBatchSection extends StatelessWidget {
  const EffectBatchSection({super.key});

  @override
  Widget build(BuildContext context) {
    final a = signal<int>(context, 1);
    final b = signal<int>(context, 2);
    final sum = computed<int>(context, (_) => a() + b());
    final effectRuns = signal<int>(context, 0);

    effect(context, () {
      sum();
      final current = untrack(() => effectRuns());
      effectRuns.set(current + 1);
    });

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('a: ${a()}  b: ${b()}  sum (computed): ${sum()}'),
        Text('effect runs: ${effectRuns()}'),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children: [
            ElevatedButton(
              onPressed: () => a.set(a() + 1),
              child: const Text('Increment A'),
            ),
            ElevatedButton(
              onPressed: () => b.set(b() + 1),
              child: const Text('Increment B'),
            ),
            OutlinedButton(
              onPressed: () {
                batch(() {
                  a.set(a() + 1);
                  b.set(b() + 1);
                });
              },
              child: const Text('Batch +1 both'),
            ),
          ],
        ),
      ],
    );
  }
}
// #endregion effect-batch-section

// #region untrack-section
class UntrackSection extends StatelessWidget {
  const UntrackSection({super.key});

  @override
  Widget build(BuildContext context) {
    final source = signal<int>(context, 1);
    final noise = signal<int>(context, 100);
    final tracked = computed<int>(context, (_) => source() + noise());
    final untracked = computed<int>(
      context,
      (_) => source() + untrack(() => noise()),
    );

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SignalBuilder(
          builder: (context) => Text('source: ${source()}  noise: ${noise()}'),
        ),
        SignalBuilder(builder: (context) => Text('tracked: ${tracked()}')),
        SignalBuilder(builder: (context) => Text('untracked: ${untracked()}')),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children: [
            ElevatedButton(
              onPressed: () => source.set(source() + 1),
              child: const Text('Bump source'),
            ),
            OutlinedButton(
              onPressed: () => noise.set(noise() + 10),
              child: const Text('Bump noise'),
            ),
          ],
        ),
        const SizedBox(height: 4),
        Text(
          'Note: untracked ignores noise changes.',
          style: Theme.of(context).textTheme.bodySmall,
        ),
      ],
    );
  }
}
// #endregion untrack-section

// #region reactive-map-section
class ReactiveMapSection extends StatelessWidget {
  const ReactiveMapSection({super.key});

  @override
  Widget build(BuildContext context) {
    final inventory = ReactiveMap<String, int>.scoped(context, {
      'apples': 2,
      'oranges': 3,
    });
    final nextId = signal<int>(context, 1);

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SignalBuilder(
          builder: (context) => Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              for (final entry in inventory.entries)
                Text('${entry.key}: ${entry.value}'),
            ],
          ),
        ),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children: [
            ElevatedButton(
              onPressed: () {
                inventory['apples'] = (inventory['apples'] ?? 0) + 1;
              },
              child: const Text('Bump apples'),
            ),
            OutlinedButton(
              onPressed: () {
                final id = nextId();
                inventory['item $id'] = id;
                nextId.set(id + 1);
              },
              child: const Text('Add item'),
            ),
            TextButton(
              onPressed: () {
                if (inventory.isNotEmpty) {
                  inventory.remove(inventory.keys.first);
                }
              },
              child: const Text('Remove first'),
            ),
          ],
        ),
      ],
    );
  }
}
// #endregion reactive-map-section

// #region reactive-set-section
class ReactiveSetSection extends StatelessWidget {
  const ReactiveSetSection({super.key});

  @override
  Widget build(BuildContext context) {
    final tags = ReactiveSet<String>.scoped(context, {'flutter', 'signals'});
    final nextId = signal<int>(context, 1);

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SignalBuilder(
          builder: (context) => Wrap(
            spacing: 8,
            children: [for (final tag in tags) Chip(label: Text(tag))],
          ),
        ),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children: [
            ElevatedButton(
              onPressed: () {
                final id = nextId();
                tags.add('tag-$id');
                nextId.set(id + 1);
              },
              child: const Text('Add tag'),
            ),
            OutlinedButton(
              onPressed: tags.isEmpty ? null : () => tags.remove(tags.first),
              child: const Text('Remove one'),
            ),
            TextButton(
              onPressed: () => tags.clear(),
              child: const Text('Clear'),
            ),
          ],
        ),
      ],
    );
  }
}
// #endregion reactive-set-section

// #region async-data-section
class AsyncDataSection extends StatelessWidget {
  const AsyncDataSection({super.key});

  @override
  Widget build(BuildContext context) {
    final requestId = signal<int>(context, 1);
    final result = useAsyncData<String>(context, () async {
      final id = requestId();
      await Future<void>.delayed(const Duration(milliseconds: 500));
      return 'Result #$id';
    }, defaults: () => 'Idle');

    final status = result.when(
      context: context,
      idle: (data) => 'Idle: ${data ?? '-'}',
      pending: (data) => 'Loading... ${data ?? ''}',
      success: (data) => 'Success: ${data ?? '-'}',
      error: (error) => 'Error: ${error?.error ?? 'unknown'}',
    );

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('request id: ${requestId()}'),
        Text(status),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children: [
            ElevatedButton(
              onPressed: () => requestId.set(requestId() + 1),
              child: const Text('Next request'),
            ),
            OutlinedButton(
              onPressed: () async {
                await result.refresh();
              },
              child: const Text('Refresh'),
            ),
          ],
        ),
      ],
    );
  }
}
// #endregion async-data-section

// #region walkthrough-section
class WalkthroughSection extends StatelessWidget {
  const WalkthroughSection({super.key});

  @override
  Widget build(BuildContext context) {
    final query = signal<String>(context, '');
    final items = ReactiveList.scoped(context, [
      'Aurora',
      'Comet',
      'Nebula',
      'Orion',
      'Pulsar',
    ]);
    final nextId = signal<int>(context, 1);
    final controller = useMemoized(context, () => TextEditingController());

    onUnmounted(context, controller.dispose);

    final filtered = computed<List<String>>(context, (_) {
      final q = query().trim().toLowerCase();
      if (q.isEmpty) return List.unmodifiable(items);
      return items.where((item) => item.toLowerCase().contains(q)).toList();
    });

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        TextField(
          key: const Key('walkthrough-query'),
          controller: controller,
          decoration: InputDecoration(
            labelText: 'Search',
            suffixIcon: IconButton(
              tooltip: 'Clear filter',
              onPressed: () {
                controller.clear();
                query.set('');
              },
              icon: const Icon(Icons.clear),
            ),
          ),
          onChanged: query.set,
        ),
        const SizedBox(height: 8),
        SignalBuilder(
          builder: (context) {
            final results = filtered();
            final total = items.length;
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Wrap(
                  spacing: 8,
                  children: [
                    ElevatedButton(
                      onPressed: () {
                        items.add('Nova ${nextId()}');
                        nextId.set(nextId() + 1);
                      },
                      child: const Text('Add item'),
                    ),
                    OutlinedButton(
                      onPressed: total == 0 ? null : () => items.removeLast(),
                      child: const Text('Remove last'),
                    ),
                    TextButton(
                      onPressed: () {
                        controller.clear();
                        query.set('');
                      },
                      child: const Text('Clear filter'),
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                Text('showing ${results.length} of $total'),
                const SizedBox(height: 4),
                for (final item in results) Text(item),
              ],
            );
          },
        ),
      ],
    );
  }
}
// #endregion walkthrough-section

// #region checkout-workflow-section
class CheckoutWorkflowSection extends StatelessWidget {
  const CheckoutWorkflowSection({super.key});

  @override
  Widget build(BuildContext context) {
    final qty = signal<int>(context, 1);
    final unitPrice = signal<double>(context, 19.0);
    final discount = signal<double>(context, 0.0);
    final saves = signal<int>(context, 0);

    final subtotal = computed<double>(context, (_) => qty() * unitPrice());
    final total = computed<double>(
      context,
      (_) => subtotal() * (1 - discount()),
    );

    effect(context, () {
      total();
      final current = untrack(() => saves());
      saves.set(current + 1);
    });

    String money(double value) => value.toStringAsFixed(2);
    String percent(double value) => '${(value * 100).round()}%';

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('qty: ${qty()}'),
        Text('unit: \$${money(unitPrice())}'),
        Text('subtotal: \$${money(subtotal())}'),
        Text('discount: ${percent(discount())}'),
        Text('total: \$${money(total())}'),
        Text('autosave runs: ${saves()}'),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children: [
            ElevatedButton(
              onPressed: () => qty.set(qty() + 1),
              child: const Text('Add 1 item'),
            ),
            OutlinedButton(
              onPressed: qty() <= 1 ? null : () => qty.set(qty() - 1),
              child: const Text('Remove 1 item'),
            ),
            TextButton(
              onPressed: () => discount.set(discount() == 0 ? 0.1 : 0.0),
              child: const Text('Toggle 10% promo'),
            ),
            TextButton(
              onPressed: () => unitPrice.set(24.0),
              child: const Text('Set price = 24'),
            ),
          ],
        ),
      ],
    );
  }
}
// #endregion checkout-workflow-section

// #region form-workflow-section
class FormWorkflowSection extends StatelessWidget {
  const FormWorkflowSection({super.key});

  @override
  Widget build(BuildContext context) {
    final name = signal<String>(context, '');
    final email = signal<String>(context, '');
    final dirty = signal<bool>(context, false);
    final lastSaved = signal<String>(context, 'never');

    final isValid = computed<bool>(context, (_) {
      final n = name().trim();
      final e = email().trim();
      return n.length >= 2 && e.contains('@');
    });

    final canSubmit = computed<bool>(context, (_) {
      return dirty() && isValid();
    });

    final submit = useAsyncData<String>(context, () async {
      await Future<void>.delayed(const Duration(milliseconds: 600));
      return 'Saved ${name()} <${email()}>';
    });

    effect(context, () {
      if (submit.status == AsyncStatus.success) {
        lastSaved.set('just now');
      }
    });

    final nameController = useMemoized(context, () => TextEditingController());
    final emailController = useMemoized(context, () => TextEditingController());
    onUnmounted(context, () {
      nameController.dispose();
      emailController.dispose();
    });

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        TextField(
          key: const Key('form-name'),
          controller: nameController,
          decoration: const InputDecoration(labelText: 'Name'),
          onChanged: (value) {
            name.set(value);
            dirty.set(true);
          },
        ),
        const SizedBox(height: 8),
        TextField(
          key: const Key('form-email'),
          controller: emailController,
          decoration: const InputDecoration(labelText: 'Email'),
          onChanged: (value) {
            email.set(value);
            dirty.set(true);
          },
        ),
        const SizedBox(height: 8),
        SignalBuilder(
          builder: (context) {
            final valid = isValid();
            final can = canSubmit();
            final status = switch (submit.status) {
              AsyncStatus.idle => 'Idle',
              AsyncStatus.pending => 'Saving...',
              AsyncStatus.success => submit.data ?? 'Saved',
              AsyncStatus.error => 'Error: ${submit.error?.error ?? 'unknown'}',
            };
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('valid: ${valid ? 'yes' : 'no'}'),
                Text('can submit: ${can ? 'yes' : 'no'}'),
                Text('status: $status'),
                Text('last saved: ${lastSaved()}'),
                const SizedBox(height: 8),
                ElevatedButton(
                  onPressed: can ? () async => submit.refresh() : null,
                  child: const Text('Save'),
                ),
              ],
            );
          },
        ),
      ],
    );
  }
}

// #endregion form-workflow-section
18
likes
160
points
477
downloads

Publisher

verified publishermedz.dev

Weekly Downloads

A high-performance Flutter state management tool, Oref is one of the fastest Flutter signals and state management solutions.

Repository (GitHub)
View/report issues

Topics

#signals #reactive #state-management #alien-signals

Documentation

API reference

Funding

Consider supporting this project:

github.com
opencollective.com

License

MIT (license)

Dependencies

alien_signals, analysis_server_plugin, analyzer, analyzer_plugin, flutter

More

Packages that depend on oref