Ask DeepWiki

Kaeru for Flutter

Kaeru is a comprehensive and efficient reactivity system for Flutter, inspired by Vue 3's @vue/reactivity. It provides a fully functional reactive programming model that makes state management in Flutter simple, optimized, and declarative.

🚀 Core Features

  • Fully reactive state management with Ref, Computed, AsyncComputed, and watchEffect.
  • Automatic dependency tracking for efficient updates.
  • Supports both synchronous and asynchronous computed values.
  • Optimized UI updates with Watch and KaeruMixin.
  • Seamless integration with ChangeNotifier and ValueNotifier.

Tip

If in KaeruMixin you have use watch$ with watch and watchEffect$ with watchEffect

📦 Installation

Add this package to your pubspec.yaml:

dependencies:
  kaeru:
    git:
      url: https://github.com/tachibana-shin/flutter_kaeru.git

Import it in your project:

import 'package:kaeru/kaeru.dart';

New API KaeruWidget

The setup() method returns a Widget Function(), which acts as your build method.

class CounterWidget extends KaeruWidget<CounterWidget> {
  final int depend;
  const CounterWidget({super.key, required this.depend});

  @override
  Setup setup() {
    final count = ref(0);
    final depend = prop((w) => w.depend);

    watchEffect(() {
      count.value; // track
      debugPrint('effect in count changed ${count.value}');
    });
    watchEffect(() {
      depend.value; // track
      debugPrint('effect in depend changed ${depend.value}');
    });

    onMounted(() {
      debugPrint('mounted');
    });

    return () {
      debugPrint('main counter re-build');

      return Row(children: [
        Watch(() {
          debugPrint('depend in counter changed');
          return Text('depend = ${depend.value}');
        }),
        Watch(() {
          debugPrint('counter in counter changed');
          return Text('counter = ${count.value}');
        }),
        IconButton(onPressed: () => count.value++, icon: const Icon(Icons.add)),
        IconButton(
            onPressed: () => count.value--, icon: const Icon(Icons.remove)),
      ]);
    };
  }
}

You can even create compositions with this

Computed<double> useScaleWidth(Ref<double> ref) {
  final ctx = useKaeruContext();
  assert(ctx != null); // Ensure if use composition without KaeruWidget

  final screenWidth = ctx.size.width;
  return computed(() => ref.value / screenWidth);
}
  • All hooks life and reactivity ready
  • useContext() -> BuildContext
  • useWidget<T> -> T is Widget

Kaeru Hooks & Widgets (Full List)

1. Lifecycle / Core Hooks

Hook / Composable Description Notes
onBeforeUnmount Register a callback to run before widget unmounts Auto dispose / cleanup
onUpdated Register a callback when reactive value changes Works with Computed or Ref
useLifeContext Access current Kaeru lifecycle context Internal reactive context
useKaeruContext Access current Kaeru reactive context Returns current KaeruMixin
onMounted Register a callback after widget is mounted Runs once
onUnmounted Register a callback after widget is disposed Runs once

2. Reactive / Controller Hooks (New)

Hook / Composable Description Notes
useTabController Creates a TabController with automatic dispose Requires TickerProvider
useAnimationController Creates an AnimationController with automatic dispose Needs vsync
useScrollController Creates a ScrollController with automatic dispose For ListView / ScrollView
useTextEditingController Creates a TextEditingController with automatic dispose Can bind reactively
usePageController Creates a PageController with automatic dispose For PageView
useValueNotifier<T> Creates a ValueNotifier<T> with automatic dispose Reactive Kaeru binding
useFocusNode Creates a FocusNode with automatic dispose For focus management
useTicker Creates a low-level Ticker with automatic dispose Custom animation without AnimationController
useKeepAliveClient Creates a KeepAliveClient with automatic dispose Simply calling this hook will make your widget behave as if it were a widget with KeepAliveClientMixin.
useRestoration Creates a RestorationController with automatic dispose For restoration management
useSingleTickerState Creates a SingleTickerProviderStateMixin with automatic dispose For custom animation without AnimationController

3. Asynchronous Data Fetching Hooks

Hook / Composable Description
useRequest Manages asynchronous data fetching with support for loading, error, and data states.
usePagination Simplifies paginated data fetching.
useLoadMore Handles "load more" or infinite scrolling logic.
usePolling Repeatedly calls a service at a specified interval.

Usage Examples

useRequest

For basic data fetching.

class UserWidget extends KaeruWidget {
  @override
  Setup setup() {
    final controller = useRequest(() => fetchUser('123'));

    return () {
      if (controller.loading.value) {
        return CircularProgressIndicator();
      }
      if (controller.error.value != null) {
        return Text('Error: ${controller.error.value}');
      }
      return Text('User: ${controller.data.value?.name}');
    };
  }
}

usePagination

For paginated data.

class UserList extends KaeruWidget {
  @override
  Setup setup() {
    final controller = usePagination((page, pageSize) => fetchUsers(page, pageSize));

    return () {
      return Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: controller.data.value.length,
              itemBuilder: (context, index) {
                final user = controller.data.value[index];
                return ListTile(title: Text(user.name));
              },
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              IconButton(
                icon: Icon(Icons.arrow_back),
                onPressed: controller.page.value > 1 ? () => controller.changePage(controller.page.value - 1) : null,
              ),
              Text('Page ${controller.page.value}'),
              IconButton(
                icon: Icon(Icons.arrow_forward),
                onPressed: () => controller.changePage(controller.page.value + 1),
              ),
            ],
          ),
        ],
      );
    };
  }
}

useLoadMore

For infinite scrolling.

class PostList extends KaeruWidget {
  @override
  Setup setup() {
    final controller = useLoadMore((page, lastItem) => fetchPosts(page));

    return () {
      return ListView.builder(
        itemCount: controller.data.value.length + 1,
        itemBuilder: (context, index) {
          if (index == controller.data.value.length) {
            return TextButton(
              onPressed: controller.loadMore,
              child: Text('Load More'),
            );
          }
          final post = controller.data.value[index];
          return ListTile(title: Text(post.title));
        },
      );
    };
  }
}

usePolling

For periodically fetching data.

class Notifications extends KaeruWidget {
  @override
  Setup setup() {
    final controller = usePolling(() => fetchNotifications(), interval: Duration(seconds: 10));

    return () {
      if (controller.loading.value && controller.data.value == null) {
        return CircularProgressIndicator();
      }
      return Badge(
        label: Text('${controller.data.value?.length ?? 0}'),
        child: Icon(Icons.notifications),
      );
    };
  }
}

4. UI & Layout Hooks

Hook / Composable Description
useTheme Returns the current ThemeData from the widget tree.
useDark Returns a reactive boolean Ref that is true if the current theme is dark.
useWidgetSize Reactively provides the Size of a widget using a LayoutBuilder.
useWidgetBox Reactively provides the BoxConstraints of a widget using a LayoutBuilder.
useStream Subscribes to a stream and returns a reactive Ref with its latest value.
useStreamDefault Similar to useStream, but with a default value.

5. Stream Hooks

Hook / Composable Description
useStream Subscribes to a stream and returns a reactive Ref with its latest value.
useStreamDefault Similar to useStream, but with a default value.

useStream

Subscribes to a stream and provides its data as a reactive Ref.

class StreamExample extends KaeruWidget {
  @override
  Setup setup() {
    final stream = Stream.periodic(Duration(seconds: 1), (i) => i);
    final value = useStream(stream);

    return () => Text('Value: ${value.value ?? 'Loading...'}');
  }
}

useStreamDefault

Similar to useStream, but it uses a default value until the stream provides its first item.

class StreamDefaultExample extends KaeruWidget {
  @override
  Setup setup() {
    final stream = Stream.periodic(Duration(seconds: 1), (i) => i);
    final value = useStreamDefault(stream, defaultValue: 0);

    return () => Text('Value: ${value.value}');
  }
}

5. Notes

  • Lifecycle hooks:
    • onBeforeUnmount, onUpdated, etc., are core reactive hooks for cleanup & reactive side effects.
  • Controller hooks:
    • Auto dispose, ready for reactive binding with Kaeru.
  • Widgets with built-in Mixins:
    • Enable Flutter core behavior without creating State manually.
  • Dynamic delegate pattern:
    • Combine multiple delegates (Animation, Scroll, Tab, ValueNotifier) with auto setup & dispose.

✨ For Large Language Models (LLMs)

This repository includes special files to help you understand and generate code for this library.

  • llm.txt: A concise guide covering the main concepts and providing a quick-start example. Use this for a general overview.
  • llm_full.txt: A comprehensive documentation file containing detailed descriptions of every public class, function, and hook. Use this as a full reference for generating detailed or complex code.

When asked to work with this library, refer to these files to ensure your generated code is accurate and idiomatic.


🏗 API Documentation

1️⃣ Reactive State: Ref<T>

Represents a reactive variable that automatically triggers updates when changed.

Parameters:

Parameter Type Description
value T The initial value of the reactive reference.

Methods:

Method Returns Description
select<U>(U Function(T value)) Computed<U> Creates a computed value derived from this Ref<T>.

Example:

final count = Ref(0);
count.addListener(() {
  print("Count changed: ${count.value}");
});

count.value++;  // ✅ Triggers update

final doubleCount = count.select((v) => v * 2);
print(doubleCount.value); // ✅ 0
count.value = 5;
print(doubleCount.value); // ✅ 10

2️⃣ Derived State: Computed<T>

Creates a computed value that automatically updates when dependencies change.

Parameters:

Parameter Type Description
getter T Function() A function that returns the computed value.

Methods:

Method Returns Description
select<U>(U Function(T value)) Computed<U> Creates a derived computed value.

Example:

final count = Ref(2);
final doubleCount = Computed(() => count.value * 2);

print(doubleCount.value); // ✅ 4
count.value++;
print(doubleCount.value); // ✅ 6

final tripleCount = doubleCount.select((v) => v * 1.5);
print(tripleCount.value); // ✅ 9

3️⃣ Effects: watchEffect & watch

watchEffect(Function callback) -> VoidCallback

  • Automatically tracks dependencies and re-executes when values change.

Example:

final stop = watchEffect$(() {
  print("Count is now: ${count.value}");
});

count.value++;  // ✅ Automatically tracks dependencies
stop(); // ✅ Stops watching

watch$(List<ChangeNotifier> sources, Function callback, {bool immediate = false}) -> VoidCallback

  • Watches multiple ChangeNotifier sources.
  • If immediate = true, executes the callback immediately.

Example:

final stop = watch$([count], () {
  print("Count changed: ${count.value}");
}, immediate: true);

stop(); // ✅ Stops watching

4️⃣ Asynchronous Derived State: AsyncComputed<T>

Handles computed values that depend on asynchronous operations.

Parameters:

Parameter Type Description
getter Future<T> Function() A function returning a future value.
defaultValue T? An optional initial value before computation completes.
beforeUpdate T? Function() An optional function to run before updating the value.
notifyBeforeUpdate bool = true Whether to notify listeners before updating the value.
onError Function(dynamic error)? An optional error handler.
immediate bool Whether to compute immediately.

Example:

final asyncData = AsyncComputed(() async {
  await Future.delayed(Duration(seconds: 1));
  return "Loaded";
}, defaultValue: "Loading", onError: (e) => print("Error: $e"), immediate: true);

print(asyncData.value);  // ✅ "Loading"
await Future.delayed(Duration(seconds: 1));
print(asyncData.value);  // ✅ "Loaded"

5️⃣ UI Integration: KaeruMixin and Watch

KaeruMixin (StatefulWidget Integration)

Allows stateful widgets to easily integrate with reactive values.

Example:

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with KaeruMixin {
  late final Ref<int> count;

  @override
  void initState() {
    super.initState();
    count = ref(0);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Watch(() => Text("Count: ${count.value}")),
        ElevatedButton(
          onPressed: () => count.value++,
          child: Text("Increment"),
        ),
      ],
    );
  }
}

Watch (Automatic UI Rebuilds)

A widget that automatically updates when its dependencies change.

Tip

If in KaeruMixin you have use watch$ with watch and watchEffect$ with watchEffect

By default Watch doesn"t care about external changes e.g.

class ExampleState extends State<Example> {
  int counter = 1;

  @override
  void initState() {
    super.initState();

    Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() {
        counter++;
      });
    });
  }

  @override
  Widget build(context) {
    return Watch(() => Text('counter = $counter')); // every is 'counter = 1'
  }
}

so if static dependency is used in Watch you need to set it in the dependencies option

class ExampleState extends State<Example> {
  int counter = 1;

  @override
  void initState() {
    super.initState();

    Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() {
        counter++;
      });
    });
  }

  @override
  Widget build(context) {
    return Watch(dependencies: [counter], () => Text('counter = $counter')); // amazing, now 'counter = 1', 'counter = 2'....
  }
}

Example:

Watch(
  () => Text("Value: ${count.value}"),
)

KaeruBuilder (Builder for Kaeru)

Example:

KaeruBuilder((context) {
  final counter = context.ref(0);

  return Watch(() => Text(counter.value.toString()));
});

6️⃣ Integration with ValueNotifier & ChangeNotifier

ValueNotifier.toRef()

Converts a ValueNotifier<T> into a Ref<T>.

Example:

final valueNotifier = ValueNotifier(0);
final ref = valueNotifier.toRef();

ref.addListener(() {
  print("Updated: ${ref.value}");
});

valueNotifier.value = 10;  // ✅ Ref updates automatically

ValueNotifier Extension

Adds .toRef() to ValueNotifier to integrate seamlessly.

7️⃣ Selector: usePick

Creates a computed value that tracks only the selected part of a reactive object, optimizing updates.

Parameters:

Parameter Type Description
ctx ReactiveNotifier<T> The reactive object to select from.
selector U Function(T value) Function to select a value from the object.

Example:

final map = Ref({'foo': 0, 'bar': 0});

Watch(() {
  // Only recomputes when 'foo' changes
  final foo = usePick(() => map.value['foo']);
  print(foo.value); // 0
});

map.value = {...map.value, 'foo': 1};

8️⃣ Cleanup: onWatcherCleanup

Registers a callback to be called when the watcher or computed is refreshed or disposed.

Parameters:

Parameter Type Description
callback VoidCallback Function to be called on cleanup/dispose.

Example:

watchEffect$(() {
  // ... reactive code ...
  onWatcherCleanup(() {
    // cleanup logic here
  });
});

// or widget Watch

Watch(() {
  onWatcherCleanup(() {
    // cleanup logic here
  });

  ////
});

// or Computed

Computed(() {
  onWatcherCleanup(() {
    // cleanup logic here
  });

  ////
});


9️⃣ Utility: nextTick

Runs a callback after the current microtask queue is flushed (similar to Promise.resolve().then() in JS).

Parameters:

Function Description
ref<T>(value) Creates a reactive reference. Access/modify its content via the .value property.
computed<T>(fn) Creates a read-only, cached value that is derived from other reactive sources.
asyncComputed<T>(fn) A version of computed for asynchronous operations, returning null until the future completes.
watchEffect(fn) Runs a function immediately and re-runs it automatically whenever any of its reactive dependencies change.
watch(sources, fn) Triggers a callback only when specific sources change.
prop<T>(fn) Creates a reactive Computed property from the parent widget's attributes.

2. Lifecycle Hooks

Manage your widget's side effects with simple lifecycle functions inside setup():

Hook Description
onMounted(fn) Called once after the widget is first inserted into the widget tree.
onUpdated(fn) Called after the widget updates.
onBeforeUnmount(fn) Called just before the widget is removed from the widget tree. Perfect for cleanup.
onDeactivated(fn) Called when the widget is deactivated.
onActivated(fn) Called when the widget is re-inserted into the tree after being deactivated.

3. Flutter Hooks

Kaeru provides use* hooks that automatically create and dispose of common Flutter objects.

Hook Description
useAnimationController() Creates and disposes of an AnimationController.
useTabController() Creates and disposes of a TabController.
useScrollController() Creates and disposes of a ScrollController.
useTextEditingController() Creates and disposes of a TextEditingController.
useFocusNode() Creates and disposes of a FocusNode.
useSingleTickerState() Provides a TickerProvider for animations.
useKeepAliveClient() Keeps a widget alive in a list (e.g., ListView).
useContext() Gets the current BuildContext.
useWidget<T>() Gets the current widget instance.

🏗 Contributing

Pull requests and feature requests are welcome! Feel free to open an issue or contribute.

📜 License

MIT License. See LICENSE for details.

Libraries

composables/define_widget/auto_dispose
composables/define_widget/bus
composables/define_widget/composables/use_context
composables/define_widget/composables/use_hover_widget
composables/define_widget/composables/use_ref
composables/define_widget/composables/use_state
composables/define_widget/composables/use_widget_bounding
composables/define_widget/composables/use_widget_size
composables/define_widget/define_widget
composables/define_widget/life
composables/define_widget/main
This library is deprecated and will be removed in a future version. Please use KaeruWidget instead.
composables/define_widget/reactivity
composables/define_widget/widget/define_widget_builder
composables/exception/no_watcher_found_exception
composables/next_tick
composables/on_watcher_cleanup
composables/shared/get_current_instance
composables/use_pick
composables/watch
composables/watch_effect
event_bus
extensions/value_notifier_to_ref
foundation/async_computed
foundation/computed
foundation/prop
foundation/ref
kaeru
A reactive state management library for Flutter, inspired by Vue Composition API.
mixins/kaeru_life_mixin
mixins/kaeru_listen_mixin
mixins/kaeru_mixin
shared/reactive_notifier
shared/watcher
widget/kaeru_widget/composables/flutter
widget/kaeru_widget/composables/use_context
widget/kaeru_widget/composables/use_dark
widget/kaeru_widget/composables/use_keep_alive_client
widget/kaeru_widget/composables/use_request/load_more/controller
widget/kaeru_widget/composables/use_request/load_more/load_more
widget/kaeru_widget/composables/use_request/pagination/controller
widget/kaeru_widget/composables/use_request/pagination/options
widget/kaeru_widget/composables/use_request/pagination/pagination
widget/kaeru_widget/composables/use_request/polling/polling
widget/kaeru_widget/composables/use_request/request/controller
widget/kaeru_widget/composables/use_request/request/options
widget/kaeru_widget/composables/use_request/request/request
widget/kaeru_widget/composables/use_request/use_request
widget/kaeru_widget/composables/use_restoration
widget/kaeru_widget/composables/use_single_ticker_state
widget/kaeru_widget/composables/use_stream
widget/kaeru_widget/composables/use_theme
widget/kaeru_widget/composables/use_widget
widget/kaeru_widget/composables/use_widget_box
widget/kaeru_widget/composables/use_widget_size
widget/kaeru_widget/kaeru_widget
widget/kaeru_widget/life
widget/kaeru_widget/reactive
widget/kareu_builder
widget/watch