Skip to content

Lifecycle Hooks

Flutter Compositions provides declarative lifecycle hooks that let you attach logic to a widget's lifecycle from within setup(). All hooks are registered during setup() and fire at the appropriate moment.

Available Hooks

onMounted

Executes after the widget is mounted on the screen (in the first frame after mount). Ideal for:

  • Making network requests
  • Initializing resources that need a rendered widget
  • Starting animations
dart
@override
Widget Function(BuildContext) setup() {
  final data = ref<String?>(null);

  onMounted(() async {
    // Safe to do async work here
    data.value = await fetchData();
  });

  return (context) => Text(data.value ?? 'Loading...');
}

onUnmounted

Executes just before the widget is destroyed (during unmount). Use it to:

  • Clean up timers, subscriptions, or event listeners
  • Cancel ongoing operations
  • Release resources not managed by use* helpers
dart
@override
Widget Function(BuildContext) setup() {
  late final Timer timer;

  onMounted(() {
    timer = Timer.periodic(const Duration(seconds: 1), (t) {
      print('Tick ${t.tick}');
    });
  });

  onUnmounted(() {
    timer.cancel();
  });

  return (context) => Container();
}

onBuild

Executes every time the builder function runs. This hook receives the current BuildContext and is primarily used internally by composables to access InheritedWidget data:

dart
@override
Widget Function(BuildContext) setup() {
  final width = ref(0.0);

  onBuild((context) {
    width.value = MediaQuery.of(context).size.width;
  });

  return (context) => Text('Width: ${width.value}');
}

TIP

For accessing InheritedWidget data reactively, prefer using useContextRef() or the built-in composables like useMediaQuery(), useTheme(), etc. They handle the onBuild integration for you.

Multiple Hooks

You can register multiple instances of the same hook. They execute in registration order:

dart
@override
Widget Function(BuildContext) setup() {
  onMounted(() => print('First mounted callback'));
  onMounted(() => print('Second mounted callback'));

  onUnmounted(() => print('First cleanup'));
  onUnmounted(() => print('Second cleanup'));

  return (context) => Container();
}

Colocating Setup and Cleanup

A common pattern is to register cleanup inside onMounted, keeping setup and teardown close together:

dart
onMounted(() {
  final subscription = stream.listen((event) {
    handleEvent(event);
  });

  onUnmounted(() {
    subscription.cancel();
  });
});

Lifecycle Mapping from StatefulWidget

StatefulWidgetFlutter Compositions
initState()Body of setup()
addPostFrameCallback in initStateonMounted()
dispose()onUnmounted()
build()Builder function returned from setup()
didUpdateWidget()Automatic via widget() reactive props
didChangeDependencies()Automatic via useContextRef() / onBuild()

Automatic Cleanup

Most resources don't need manual cleanup when you use the framework's built-in helpers:

ResourceAuto-cleaned by
Ref, ComputedRefeffectScope disposal
watch, watchEffecteffectScope disposal
ScrollController, TextEditingController, etc.use* helpers
AnimationControlleruseAnimationController
Provided valuesWidget unmount

You only need onUnmounted for external resources (timers, raw streams, platform channels, etc.) that aren't managed by composables.

Next Steps

Released under the MIT License.