Skip to Content
Forui 0.17.0 is released 🎉

Controls

Controls are abstractions over controllers, e.g. TextEditingController, that define where state lives. Instead of passing controllers to Forui widgets, you pass controls (that optionally wrap controllers).

There are 2 types of controls.

Lifted

You externally manage the state. The widget is “dumb” and just reflects the passed-in values. This is similar to React’s controlled components .

FPopover( control: .lifted( shown: _shown, onChange: (shown) => setState(() => _shown = shown), ), );

Managed

The widget internally manages its own state, either through an internal controller configured using the passed-in initial values, or through a passed-in external controller. In the latter case, you are responsible for managing the controller’s lifecycle.

// FPopover manages its own state using an internal controller. FPopover(control: .managed(initial: false, onChange: print)); // FPopover manages its own state using an external controller. FPopover(control: .managed(controller: _externalController));

When to Use Which?

TL;DR: Start with “Managed with internal controller” for simplicity and switch as needed.

Common scenarios

  • Lifted:

    • Syncing state between your state management solution, e.g. Riverpod , and the widget.
    • Reacting to every state change and potentially modifying the state.
  • Managed with external controller:

    • Using a lifecycle management solution, e.g. Flutter Hooks .
    • Programmatically triggering actions, e.g. showing a popover.
  • Managed with internal controller:

    • Prototyping.
    • Simply setting an initial value.
    • Passively observing state changes.

Custom Controllers

Suppose you extend a controller, e.g. FPopoverController, to add custom logic. You have 2 options when passing the custom controller to a widget.

Managed Control with External Controller

Pass the custom controller via a managed control.

class CustomController extends FPopoverController { // Custom logic here. } class MyWidget extends StatefulWidget { const MyWidget({super.key}); @override State<MyWidget> createState() => _MyWidgetState(); } class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin { late final CustomController _controller; @override void initState() { super.initState(); _controller = CustomController(vsync: this); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) => FPopover( control: .managed(controller: _controller), // ... ); }

The downside of this approach is that you have to manage the controller’s lifecycle yourself.

Extend Managed Control

Alternatively, you can extend managed control (FPopoverManagedControl) to create your custom controller.

class CustomController extends FPopoverController { final int customValue; CustomController({required this.customValue, required super.vsync}); } class CustomManagedControl extends FPopoverManagedControl { final int customValue; CustomManagedControl({required this.customValue}); @override CustomController createController(TickerProvider vsync) => CustomController( customValue: customValue, vsync: vsync, ); } // Usage: FPopover(control: CustomManagedControl(customValue: 42));

This way, the widget manages the controller’s lifecycle for you.

Last updated on