Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Separating UI and Logic in Flutter

Normally, I use a separate class with an object declared on the top of the widget. I wish to know what is the problem with that architecture.

I came across an entire package in Flutter, WidgetView, which needs to declare a dependency, then make a state object, and then do the same thing.

Why not just a simple class for achieving the same. like below

class NewAccountComponent extends StatelessWidget {  
final NewAccountComponentLogic logic = NewAccountComponentLogic();
  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Enter a Unique Account Number'),
      titlePadding: EdgeInsets.all(20.0),
      content: TextFormField(
        controller: logic.controller,
            onPressed: () => logic.clearTextFormField(),
          ),
        ),
}
class NewAccountComponentLogic {
  static String accountNumber;
  static bool existsAccountNumber;
  TextEditingController controller = TextEditingController();
  clearTextFormField() {
    controller.text = '';
    accountNumber = '';
}
like image 438
S Das Avatar asked Sep 15 '25 16:09

S Das


1 Answers

You can separate widget logic and presentation in many ways. One that I've seen (and that you mention) is using the WidgetView pattern. You can do it without any dependency:

  1. Create an abstract class thats contains the logic that all WidgetViews should be implement:

For Stateless widgets:

abstract class StatelessView<T1> extends StatelessWidget {
  final T1 widget;
  const StatelessView(this.widget, {Key key}) : super(key: key);
  
  @override
  Widget build(BuildContext context);
}

For Stateful widgets:

abstract class WidgetView<T1, T2> extends StatelessWidget {
  final T2 state;
  T1 get widget => (state as State).widget as T1;

  const WidgetView(this.state, {Key key}) : super(key: key);
  
  @override
  Widget build(BuildContext context);
}
  1. Create your widget normmally:
// Note it's a StatefulWidget because accountNumber mutates
class NewAccountComponent extends StatefulWidget {
  @override
  _NewAccountComponentState createState() => _NewAccountComponentState();
}

class _NewAccountComponentState extends State<NewAccountComponent> {
  String accountNumber;
  bool existsAccountNumber;
  final TextEditingController controller = TextEditingController();

  clearTextFormField() {
    controller.text = '';
    accountNumber = '';
  }

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Enter a Unique Account Number'),
      titlePadding: EdgeInsets.all(20.0),
      content: TextFormField(
        controller: controller,
        onSaved: (value) => clearTextFormField(),
      ),
    );
  }
}
  1. If the widget is a Stateful
class NewAccountComponent extends StatefulWidget {
  @override
  _NewAccountComponentController createState() => _NewAccountComponentController();
}

// State suffix renamed to Controller
// This class has all widget logic
class _NewAccountComponentController extends State<NewAccountComponent> {
  String accountNumber;
  bool existsAccountNumber;
  final TextEditingController controller = TextEditingController();

  clearTextFormField() {
    controller.text = '';
    accountNumber = '';
  }

  // In build, returns a new instance of your view, sending the current state
  @override
  Widget build(BuildContext context) => _NewAccountComponentView(this);
}

// View extends of WidgetView and has a current state to access widget logic
// with widget you can access to StatefulWidget parent
class _NewAccountComponentView
    extends WidgetView<NewAccountComponent, _NewAccountComponentController> {

  _NewAccountComponentView(_NewAccountComponentController state): super(state);

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Enter a Unique Account Number'),
      titlePadding: EdgeInsets.all(20.0),
      content: TextFormField(
        controller: state.controller,
        onSaved: (value) => state.clearTextFormField(),
      ),
    );
  }
}
  1. If it's Stateless, change from:
class MyStatelessWidget extends StatelessWidget {
  final String textContent = "Hello!";

  const MyStatelessWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(textContent),
    );
  }
}

to:

// Widget and logic controller are unit
class MyStatelessWidget extends StatelessWidget {
  final String textContent = "Hello!";

  const MyStatelessWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) => _MyStatelessView(this);
}

// The view is separately
class _MyStatelessView extends StatelessView<MyStatelessWidget> {
  _MyStatelessView(MyStatelessWidget widget) : super(widget);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(widget.textContent),
    );
  }
}

References:

Flutter: WidgetView — A Simple Separation of Layout and Logic

like image 136
Ignacior Avatar answered Sep 17 '25 08:09

Ignacior