Widget Controller

To update a widget's state from parents, Flutter provides various ways: setState, ChangeNotifier, ... We also want to suggest an alternative - Controller.

Take an example from our tutorial, the BottomBar.

class BottomBarController extends Controller<_BottomBarState> {
  int? get getIndex => currentState?.index;

  set setIndex(int value) => currentState?.index = value;
}

class BottomBar extends StatefulWidget {
  const BottomBar({
    BottomBarController? controller,
    this.index = 0,
    this.width,
    this.height,
    this.bottomPadding = 0,
    this.onChanged,
  }) : super(key: controller);

  final int index;
  final double? width;
  final double? height;
  final double bottomPadding;
  final void Function(int index)? onChanged;

  
  State<BottomBar> createState() => _BottomBarState();
}

class _BottomBarState extends State<BottomBar> {
  late int _index;
  int get index => _index;
  set index(int value) => setState(() => _index = value);

  
  void initState() {
    super.initState();
    index = widget.index;
  }

  
  Widget build(BuildContext context) {
    const topPadding = 25.0;
    return Container(
      color: ColorDecor().xFFFFFF,
      padding: EdgeInsets.only(top: topPadding, bottom: widget.bottomPadding),
      constraints: BoxConstraints.tightFor(
        width: widget.width,
        height: (widget.height ?? 0) + topPadding,
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          GestureDetector(
            onTap: onCoinsTap,
            child: Icon(
              Icons.paid_rounded,
              color: index == 0 ? Color(0xFF8BC540) : Colors.grey,
            ),
          ),
          GestureDetector(
            onTap: onAccountTap,
            child: Icon(
              Icons.category_rounded,
              color: index == 1 ? Color(0xFF8BC540) : Colors.grey,
            ),
          ),
        ],
      ),
    );
  }

  void onCoinsTap() {
    index = 0;
    widget.onChanged?.call(index);
  }

  void onAccountTap() {
    index = 1;
    widget.onChanged?.call(index);
  }
}

Focusing on BottomBarController, we can see it extends from Controller. In a nutshell, Controller is a GlobalKey. On the other hand, Controller plays a role similar to Agent, as a reference to an object, the widget in this case. Controller can:

  • prevent the widget from exposing the State like GlobalKey.

  • update the State directly without needing to wrap it with other State management widgets (ValueListenableBuilder, etc.), which simplifies the widget tree.

  • narrow the scope of widgets that need to update states instead of the whole widget (setState).

Controller shall be set up with APIs for updating state.

class BottomBarController extends Controller<_BottomBarState> {
  int? get getIndex => currentState?.index;

  set setIndex(int value) => currentState?.index = value;
}