solidart_hooks

For a comprehensive and updated documentation go to The Official Documentation


Helper library to make working with solidart in flutter_hooks easier.

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:solidart_hooks/solidart_hooks.dart';

class Example extends HookWidget {
  const Example({super.key});

  @override
  Widget build(BuildContext context) {
    final count = useSignal(0);
    final doubleCount = useComputed(() => count.value * 2);
    useSolidartEffect(() {
      debugPrint('Effect count: ${count.value}, doubleCount: ${doubleCount.value}');
    });
    return Scaffold(
      body: Center(
        child: SignalBuilder(
          builder: (context, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Count: ${count.value}'),
                Text('Double: ${doubleCount.value}'),
              ],
            );
          }
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => count.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

As you can see, a SignalBuilder is used to rebuild the widget when the signal changes. This is a good practice to avoid rebuilding the entire widget tree when a signal changes, and only rebuild the parts that depend on the signal. Alternatively, you can use useListenable from flutter_hooks to listen to the signal changes, but this will rebuild the entire widget tree; for example:

class UseSignalExample extends HookWidget {
  const UseSignalExample({super.key});

  @override
  Widget build(BuildContext context) {
    final count = useSignal(0);
    // this will rebuild the entire widget when the signal changes
    useListenable(count);

    return Scaffold(
      appBar: AppBar(title: const Text('useSignal')),
      body: Center(child: Text('Count: ${count.value}')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => count.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

useSignal

How to create a new signal inside of a hook widget:

class Example extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final count = useSignal(0);
    return Scaffold(
      body: Center(
        child: SignalBuilder(
          builder: (context, child) {
            return Text('Count: ${count.value}');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => count.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

The widget will automatically rebuild when the value changes. The signal will get disposed when the widget gets unmounted.

useComputed

How to create a new computed signal inside of a hook widget:

class Example extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final count = useSignal(5);
    final doubled = useComputed(() => count.value * 2);
    return Scaffold(
      body: Center(
        child: SignalBuilder(
          builder: (context, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Count: ${count.value}'),
                Text('Doubled: ${doubled.value}'),
              ],
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => count.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

The widget will automatically rebuild when the value changes. The computed will get disposed when the widget gets unmounted.

useSolidartEffect

How to create a new effect inside of a hook widget:

class Example extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final count = useSignal(0);
    useSolidartEffect(() {
      debugPrint('Effect triggered! Count: ${count.value}');
    });
    return Scaffold(
      body: Center(
        child: SignalBuilder(
          builder: (context, child) {
            return Text('Count: ${count.value}');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => count.value++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

useListSignal

How to create a new list signal inside of a hook widget:

class Example extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final items = useListSignal<String>(['Item1', 'Item2']);
    return Scaffold(
      body: Center(
        child: SignalBuilder(
          builder: (context, child) {
            return Text('Items: ${items.value.join(', ')}');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => items.add('Item${items.value.length + 1}'),
        child: const Icon(Icons.add),
      ),
    );
  }
}

The widget will automatically rebuild when the list changes. The signal will get disposed when the widget gets unmounted.

useSetSignal

How to create a new set signal inside of a hook widget:

class Example extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final uniqueItems = useSetSignal<String>({'Item1', 'Item2'});
    return Scaffold(
      body: Center(
        child: SignalBuilder(
          builder: (context, child) {
            return Text('Items: ${uniqueItems.value.join(', ')}');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => uniqueItems.add('Item${uniqueItems.value.length + 1}'),
        child: const Icon(Icons.add),
      ),
    );
  }
}

The widget will automatically rebuild when the set changes. The signal will get disposed when the widget gets unmounted.

useMapSignal

How to create a new map signal inside of a hook widget:

class Example extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final userRoles = useMapSignal<String, String>({'admin': 'John'});
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SignalBuilder(
              builder: (context, child) {
                return Text(
                  'Roles: ${userRoles.value.entries.map((e) => '${e.key}:${e.value}').join(', ')}',
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
      floatingActionButton: FloatingActionButton(
        onPressed: () => userRoles['user${userRoles.value.length}'] = 'User${userRoles.value.length}',
        child: const Icon(Icons.add),
      ),
    );
  }
}

The widget will automatically rebuild when the map changes. The signal will get disposed when the widget gets unmounted.

useResource

How to create a new resource inside of a hook widget:

class Example extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final userResource = useResource(() async {
      await Future.delayed(const Duration(seconds: 1));
      return 'Data loaded';
    });

    return Scaffold(
      body: Center(
        child: SignalBuilder(
          builder: (context, child) {
            return userResource.state.on(
              ready: (data) => Text('Result: $data'),
              error: (error, stackTrace) => Text('Error: $error'),
              loading: () => const CircularProgressIndicator(),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => userResource.refresh(),
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

The resource will get disposed when the widget gets unmounted.

useResourceStream

How to create a new resource from a stream inside of a hook widget:

class Example extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final streamResource = useResourceStream<int>(() {
      return Stream.periodic(const Duration(seconds: 1), (count) => count);
    });

    return Scaffold(
      body: Center(
        child: SignalBuilder(
          builder: (context, child) {
            return streamResource.state.on(
              ready: (data) => Text('Stream value: $data'),
              error: (error, stackTrace) => Text('Error: $error'),
              loading: () => const CircularProgressIndicator(),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => streamResource.refresh(),
        child: const Icon(Icons.refresh),
      ),
    );
  }
}

The resource will get disposed when the widget gets unmounted.

useExistingSignal

How to bind an existing signal inside of a hook widget:

class UseExistingSignalExample extends StatefulHookWidget {
  const UseExistingSignalExample({super.key});

  @override
  State<UseExistingSignalExample> createState() =>
      _UseExistingSignalExampleState();
}

class _UseExistingSignalExampleState extends State<UseExistingSignalExample> {
  final existingSignal = Signal(42);

  @override
  Widget build(BuildContext context) {
    final boundSignal = useExistingSignal(existingSignal);

    return Scaffold(
      body: Center(
        child: SignalBuilder(
          builder: (context, child) {
            return Text('Value: ${boundSignal.value}');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => existingSignal.value++,
        child: const Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    existingSignal.dispose();
    super.dispose();
  }
}

The signal will NOT get disposed when the widget gets unmounted (unless autoDispose is true).

Libraries

solidart_hooks
A Dart library for Solidart Hooks, providing Flutter Hooks bindings for Solidart.