Separating UI and Logic in Flutter
Solution 1
I've modified your code a little bit. If you change your code as like the following code, hopefully, you will get the expected output.
class NewAccountComponent extends StatelessWidget {
final NewAccountComponentLogic logic = NewAccountComponentLogic(
'123456',
true,
TextEditingController(),
);
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Enter a Unique Account Number'),
titlePadding: EdgeInsets.all(20.0),
content: TextFormField(
controller: logic.controller,
),
actions: <Widget>[
TextButton(
child: Text('Done'),
onPressed: () {
print(logic.controller.text);
logic.clearTextFormField();
},
),
],
);
}
}
class NewAccountComponentLogic {
String accountNumber;
bool existsAccountNumber;
TextEditingController controller;
NewAccountComponentLogic(
this.accountNumber,
this.existsAccountNumber,
this.controller,
);
void clearTextFormField() {
controller.text = '';
accountNumber = '';
}
@Ignacior has also given a nice solution which you can follow.
Solution 2
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:
- 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);
}
- 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(),
),
);
}
}
- 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(),
),
);
}
}
- 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
S Das
Updated on December 29, 2022Comments
-
S Das over 1 year
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 = ''; }
-
S Das about 3 yearsThanks for your detailed explaination.But why not a simple class, can do the job...why do I require to extend the stateless widget for the logic as well.
-
Ignacior about 3 yearsSure, the idea of separating it into a single class that contains all the logic is valid. I'm just showing one way to do it and following the rules of the pattern: Each State (or StatelessWidget), has a child WidgetView, which contains the declarative view code. The State acts as a stand-in Controller / Mediator / Presenter for the WidgetView, responding to view events and providing access to state. The WidgetView is a just a StatelessWidget that is pure layout.
-
Ignacior about 3 yearsYou can follow the approach that fits the problem better. For widgets with little logic I think it is not necessary to divide into multiple parts, much less apply the pattern that I have mentioned.
-
S Das about 3 yearsThanks a lot. I am very clear of the issue now.