For the past eight months, I’ve been coding and learning SwiftUI to build iOS native apps. Now, I’m collaborating with a friend to develop his app in Flutter. This experience brought perspective to my knowledge of reactive programming and also helped me deepen some of the newer mobile app development concepts. As an entrepreneur, I love what Flutter promises, one codebase to conquer many platforms. However, as a developer, I feel that vanilla Flutter is messy and doesn’t seem to promote coding best practices. Thus, I took inspiration from SwiftUI and implemented some tricks to enhance delivery speed and clarity. I’ll walk you through what I did in today’s post.

The Clutter of Flutter
Flutter leverages Dart’s code-generation capabilities to translate code into a native form. Then it builds native apps using each platform’s specific framework. To build UIs, it embraces the reactive programming paradigm and offers many out-of-the-box components to create stunningly-looking apps.
One major flaw I saw about their coding style is that they favored compiler optimizations over the readability of the code. Let me illustrate it with some sample code.
class ExampleA extends StatelessWidget { | |
const ExampleA({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: Center( | |
child: Container( | |
decoration: BoxDecoration( | |
border: Border.all(width: 1.0), | |
borderRadius: BorderRadius.circular(8.0)), | |
child: const Padding( | |
padding: EdgeInsets.all(20), | |
child: Text("Hello flutter World"), | |
), | |
), | |
), | |
); | |
} | |
} |
All that code does is draw a text string in the center of the device’s screen within a rectangle of rounded corners. It looks like this.

If you pay close attention to the snippet, you’ll notice those const keywords. These keywords define Dart’s compile-time constants; it uses them to perform optimizations compile-time. However, the code editor keeps showing warnings if you don’t add them. Thus, in theory, it can infer where to do those optimizations without the developer explicitly stating it and avoid placing an unnecessary burden on them, who might not even understand why or when to use those constructs.
The next thing we notice is the nesting level. To make the problem even more apparent, I will stack two boxes together using a column component. One would imagine that a simple copy/paste and embedding content within a column would suffice, right?
class ExampleB extends StatelessWidget { | |
const ExampleB({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: SizedBox.expand( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: [ | |
Padding( | |
padding: const EdgeInsets.all(6), | |
child: Container( | |
decoration: BoxDecoration( | |
border: Border.all(width: 1.0), | |
borderRadius: BorderRadius.circular(8.0)), | |
child: const Padding( | |
padding: EdgeInsets.all(20), | |
child: Text("Hello flutter World"), | |
), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.all(6), | |
child: Container( | |
decoration: BoxDecoration( | |
border: Border.all(width: 1.0), | |
borderRadius: BorderRadius.circular(8.0)), | |
child: const Padding( | |
padding: EdgeInsets.all(20), | |
child: Text("Hello flutter World"), | |
), | |
), | |
), | |
], | |
), | |
), | |
); | |
} | |
} |
The code has nearly doubled, and now we not only have to balance out constructor parentheses, but also, there could be square braces!
Another problem of working like this is that the boilerplate obscures almost completely the intent, which is the two text boxes.
If we convert the square box into a component, we could cut part of the clutter off, but the readability problem persists.

SwiftUI to the Rescue!
I loved working with SwuiftUI because of how concise the DSL to build components was. Don’t get me wrong, SwiftUI still has to create a widget tree with that many nesting levels, but it introduces an easy-to-read and easy-to-invoke syntax. For example, this syntax helps me wrap any component with a Padding element by calling .padding() within that element. This little tweak makes a remarkable difference when reading code and placing components.
Thankfully, Dart features class extensions that we can leverage to make life easy. So let’s introduce a few extensions and see how they can simplify the previous code.
import 'package:flutter/material.dart'; | |
extension QuickPadding on Widget { | |
Widget padding( | |
{double? all, | |
double top = 0, | |
double bottom = 0, | |
double left = 0, | |
double right = 0}) { | |
EdgeInsets insets = (all != null) | |
? EdgeInsets.all(all) | |
: EdgeInsets.only(bottom: bottom, top: top, left: left, right: right); | |
return Padding( | |
padding: insets, | |
child: this, | |
); | |
} | |
} | |
extension QuickSizedBox on Widget { | |
Widget sizedBox({double? width, double? height}) { | |
return SizedBox( | |
width: width, | |
height: height, | |
child: this, | |
); | |
} | |
Widget boxExpand() { | |
return SizedBox.expand( | |
child: this, | |
); | |
} | |
} | |
extension QuickColumn on List<Widget> { | |
Widget column( | |
{CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, | |
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.center}) { | |
return Column( | |
crossAxisAlignment: crossAxisAlignment, | |
mainAxisAlignment: mainAxisAlignment, | |
children: this, | |
); | |
} | |
} | |
extension QuickContainer on Widget { | |
Widget container({Decoration? decoration}) { | |
return Container( | |
decoration: decoration, | |
child: this, | |
); | |
} | |
} |
And now, we can apply it to the previous example and look at the difference!
class ExampleC extends StatelessWidget { | |
const ExampleC({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: [ | |
const Text("Hello flutter World") | |
.padding(all: 20) | |
.container( | |
decoration: BoxDecoration( | |
border: Border.all(width: 1.0), | |
borderRadius: BorderRadius.circular(8.0)), | |
) | |
.padding(all: 6), | |
const Text("Hello flutter World") | |
.padding(all: 20) | |
.container( | |
decoration: BoxDecoration( | |
border: Border.all(width: 1.0), | |
borderRadius: BorderRadius.circular(8.0)), | |
) | |
.padding(all: 6), | |
].column().boxExpand(), | |
); | |
} | |
} |
And now we could convert that initial box into a reusable component class or function. I’ll leave that as an exercise for the reader. This new syntax would allow very compact notation if only Dart had the same type inference system that swift has. However, it’s not bad, and a large piece of the boilerplate went away.
Closing Thoughts
Flutter can be a powerful tool when leveraged correctly. It allows quick prototyping and go-to-market of mobile apps that target multiple platforms with only one codebase. It is, however, challenging to work with because of the design choices of its designers. In addition, because the product is relatively new, it doesn’t enjoy 100% coverage of widely-accepted best practices, and it’s the responsibility of the developers and architects to help build these practices. Striving to increase readability helps build apps that other developers can understand and modify quicker, so it’s something anyone should aim to have in their projects.
I hope Dart incorporates more syntactic sugar and flutter includes easier-to-use components, but for now, we will have to build them and contribute them to the community.
Leave a Reply