Flutter in Go divan's blog #3016
Flutter in Go · divan's blog
I’ve recently discovered Flutter – a new Google’s framework for mobile development – for myself and even had an experience of teaching Flutter basics to the person who has never been doing programming at all. Flutter is written in Dart – programming language born in a Chrome browser and then escaped to the console land – and that made me think “hey, Flutter could have been easily implemented in Go as well”!
Why not? Both Go and Dart were born inside Google (and share some approaches that make them great), both strongly-typed, compiled languages – in a slightly different turn of events, Go could definitely have been a choice for such an ambitious project as Flutter. And Go is much easier to explain to the person who has never been programming before.
So let’s pretend Flutter is written in Go already. How would the code look like?
The problem with Dart
I’ve been following Dart development since the very beginning of its existence in Chrome, and my assumption has always been that Dart will replace JS eventually in every browser. It was extremely disappointing to read the news about Google ditching Dart support in Chrome back then in 2015.
Dart is fantastic! Well, everything is fantastic when you upgrade from JS, but if you downgrade from, say, Go, it’s not that exciting, but… it’s ok. Dart has every feature possible – classes/generics/exceptions/Futures/async-await/event-loop/JIT/AOT/GC/overloads - you name it. It has special syntax for getters/setters, special syntax for auto-initialization in constructors, special syntax for special syntax and many more.
While it makes Dart more familiar to people with a background in pretty much any other language – which is great and lowers entry barrier – I found it hard to explain to the newcomer with no background.
This triplet - “special”, “hidden” and “ambiguous” - probably captures the essence of what people call “magic” in programming languages. Those are features designed to help us write simpler and cleaner code, but, in fact, they add more confusion and more cognitive load to reading programs.
And that’s exactly where Go took a vastly different stance and guards its positions fiercely. Go is virtually non-magical language – it minimizes amount of special/hidden/ambiguous constructs to the lowest possible amount. It comes with its own list of shortages, however.
The problem with Go
As we’re talking about Flutter, which is a UI framework, we have to look at Go as a tool for describing/specifying UI. UI framework is a massively complicated subject to deal with – it literally requires creating a specialized language to handle the amount of essential complexity. One of the most popular ways to do so is to create DSL - Domain-specific Language – and the common knowledge is that Go is really bad at it.
Creating DSL means creating custom terms and verbs developer can use. The resulting code should capture the essence of UI layout and interactions, and be flexible enough to allow designers’ fantasy flow, but rigid enough to conform UI framework limitations. For example, you should be able to put buttons inside a container, then put icons and text widget inside buttons, and yet compiler should give you an error if you try to put the button into text.
UI specific language is also often declarative – which, in fact, means that you should be able to use code constructs (including things like space indentation!) to visually capture the structure of the UI widgets tree and then let UI framework figure out the code to run. Uff.
Some languages are more suitable for such a feat and Go never been designed to accomplish this kind of tasks. So, writing Flutter code in Go should be quite a challenge!
Ode to Flutter
If you are unfamiliar with Flutter, I highly recommend you to spend a weekend or two watching tutorials or reading docs, because it’s undoubtedly a game changer in a mobile development world. And, hopefully, not only mobile – there are renderers (embedders, in Flutter terms) for native desktop apps, and web apps. It’s easy to learn, it’s logical, has a massive collection of Material Design powered widgets, has great community and great tooling (if you like
A year ago I needed a relatively simple mobile app (for iOS and Android, obviously), but I realized that the complexity of becoming proficient in both platforms development is above any possible limits (the app was kinda side-project), so I had to outsource development to another team and pay money for that. Developing the mobile app was virtually unaffordable for someone like me – a developer with almost two decades of programming experience.
With Flutter, I wrote the same app in 3 nights, while learning this framework from scratch! It’s an order of magnitude improvement and a game changer.
Last time I remember seeing such a revolution in development productivity was 5 years ago when I discovered Go. And it changed my life.
I recommend you to start with this great video tutorial.
Flutter’s Hello, world
When you create a fresh Flutter project with
I think it’s a good example to rewrite in our imaginary Flutter in Go. It has everything relevant to our subject. Take a look at the code (it’s one file):
Let’s break down it to pieces, analyze what does and does not map well into Go, and explore the options we have.
Mapping to Go
The start is relatively straightforward – importing the dependency and starting
The only change to note is that instead of using magical
In Flutter, everything is a widget. In Dart-version of a Flutter, every widget is represented with a class extending Flutter’s special Widget classes.
Go doesn’t have classes and thus classes hierarchy, because the world is not object-oriented, let alone hierarchical. It may come as a hard truth to people familiar only with class-based OOP, but it’s really not. The world is a huge interconnected graph of things and relationships. It’s not chaotic, but neither perfectly structured and trying to fit everything into class hierarchies is a guaranteed way to make code unmaintainable, which is a most of world’s codebases as of today.
I love that Go designers put an effort to rethink this omnipresent concept of class-based OOP and came up with different OOP concept, which is, not accidentally, more closer to what OOP inventor, Alan Kay, actually meant.
In Go, we represent any abstraction with a concrete type – a structure:
In Dart-version of a Flutter,
I don’t know Flutter internals, so let’s not question if we really need 1) and implement it in Go. For that, we have only one option – embedding:
This will add all the
The second part –
We might notice a few differences with Dart’s Flutter here:
In order to make a Go’s Flutter
That was the first thing I found baffling with Dart’s Flutter. There are two kinds of widgets in Flutter –
Uff, let’s try to understand not only what is written here, but why?
The task to solve here is to add state (
Accidental complexity is all the rest. Dart’s Flutter approach is to introduce a new class
How would we design it in Go?
First of all, I personally would try to avoid creating a new concept for
The challenge, of course, is to make Flutter engine track state changes and react on it (that’s the gist of reactive programming, after all). And instead of creating “special” methods and wrappers around state changes, we can just ask the developer to manually tell Flutter when we want to update the widget. Not all state changes require immediate redrawing – there are plenty of valid cases to that. Let’s see:
There is a number of options with naming and design here – I like
But the point is we just implemented the same task of adding a reactive state to the widget, without adding:
Plus, API is cleaner and more explicit – just increase the counter and ask flutter to re-render – something not really obvious when you asked to call special function
Stateful widgets as a children
As a logical continuation, let’s take a closer look at how “stateful widget” is used within another widget in Flutter:
It turns out, Flutter uses this separation between Widget and State to hide this initialization/state-bookkeeping flow from the developer. It does create a new
To me it doesn’t make much sense – more hidden stuff, more magic and more ambiguity (we still can add Widgets as class properties and instantiate them once upon widget creation). I understand why it feels nice (no need to keep track of widget’s children) and it has the nice effect of simplifying refactoring (you have to delete constructor call in one place only to delete the child), but any developer attempting to really understand how does the whole thing works will be puzzled here.
For Go version, I definitely would prefer explicit and clear initialization of widgets with the state, even if it means more verbose code. Dart’s Flutter approach probably could be done as well, but I like Go for being non-magical, and that philosophy applies to Go frameworks as well. So, my code for stateful children widgets would look like this:
The code is more verbose, and if we had to change/replace MyHomeWidget in MyApp, we would have to touch code in 3 places, but a side effect is that we have a full and clear picture of what going on at each stage of the code execution. There is no hidden stuff happening behind the scene, we can reason about the code, about performance and dependencies of each of our types and functions with 100% confidence. And, for some, that’s the ultimate goal of writing reliable and maintainable code.
By the way, Flutter has a special widget called StatefulBuilder, which adds even more magic for hiding state management.
Now, the fun part. How do we build a Flutter’s widget tree in Go? We want to have our widgets tree concise, readable, easy to refactor and update, describe spatial relationship between widgets and add enough flexibility to plug in custom code like button press handlers and so on.
I think Dart’s Flutter version is quite beautiful and self-explanatory:
Every widget has a constructor function, which accepts optional parameters, and the real trick that makes this declarative way really nice is named function parameters.
Just in case you’re unfamiliar, in most languages parameters are called “positional parameters” because it’s their position in the function call matters:
while with named parameters, you also write their name in the function call:
It adds verbosity, but it saves you clicks and jumps over the code to understand what are those parameters all about.
In the case of UI widget tree, they play a crucial role in the readability. Consider the same code as above, without named parameters:
Meh, right? It’s not only harder to read and understand (you need to keep in your memory what each parameter mean, what’s its type and it’s a huge cognitive burden), but also leaves us with no flexibility in what parameters we really want to pass. For example, you may not want to have
As Go doesn’t have function overloading or named parameters, that’s going to be a tough task.
The temptation here might be just to replicate Dart’s way of expressing widgets tree, but what we really need is to step back and answer the question – which is the best way to represent this type of data within the constraints of the language?
Let’s take a closer look at the Scaffold object, which is a nice helper for building a decently looking modern UI. It has properties – appBar, drawer, home, bottomNavigationBar, floatingActionButton – all Widgets, by the way. And we’re creating the object of the type
Let’s try the naïve approach:
Well, not the prettiest UI code, for sure. The word
As most of the code will use
Now, instead of writing
Let’s rewrite our code:
A bit cleaner, but those nils… How can we avoid requiring concrete parameters?
Maybe reflection? Some early Go HTTP frameworks used this approach (martini for example) – you pass whatever you want via parameters, and runtime will figure out if this a known type/parameter. It’s a bad practice from many points of view - it’s unsafe, relatively slow, adds magic – but for the sake of exploration let’s try it:
Okay, a bit cleaner and similar to Dart’s original version, but lack of named parameters really hinder readability in such a case with “optional” parameters. Plus, it’s the code that really smells with bad approaches.
Let’s rethink what’s exactly we’re doing when creating new objects and optionally defining their properties? It’s just a normal variable instantiation, so what if we try it in a different way:
This approach will work, and while it solves the “named parameters issue”, it really messes up the understanding of widgets tree. First of all, it reversed the order of creating widgets – the deeper the widget, the earlier it should be defined. Second, we lost our indentation-based spatial layout of code, which is a great helper of quickly building a high-level overview of the widgets tree.
So for some people, it might be a more natural way to describe UI as a code. But it’s hard to deny that it’s definitely not the best option.
One more option I considered is creating a separate type for constructor parameters. For example:
Not bad, actually! Those
There is a way to remove
That’s an almost perfect similarity with Dart’s version! It will, however, require creating those parameters types for each widget.
Another option to explore is to use chaining of widget’s methods. I forgot the name of this pattern, but it’s not important because patterns should emerge from code, not the opposite way.
The basic idea is that upon creating a widget – say
Let’s try to rewrite our Scaffold-based widget that way:
It’s not an alien concept – many Go libraries use a similar approach for the configuration options, for example. It’s a little bit different from original Dart’s one, but holds most of the desired properties:
I also like conventional Go’s
Anyway, from all the options I explored, the last two options are probably the most appropriate ones.
Now, assembling all pieces together, that’s how I would say Flutter’s “hello, world” app looks like:
I actually quite like it.
Similarity with Vecty
I could not help but notice how similar my final result to what Vecty framework provides. Basically, the general design is almost the same, it’s just Vecty outputs into DOM/CSS, while Flutter goes deeper into fully-fledged native rendering layers for providing crazy smooth 120fps experience with beautiful widgets (and solves a bunch of other problems). I think Vecty design is exemplary here, and no wonder my result ended up being a “Vecty adaptation for Flutter” :)
Understanding Flutter’s design better
This thought experiment has been interesting by itself – not every day you have to write (and explore!) code for the library/framework that has never been written. But it also helped me to dissect Flutter’s design a little bit deeper, read some technical docs, and uncover layers of hidden magic behind Flutter.
My verdict to the question “Can Flutter be written in Go?” is definitely yes, but I’m biased, not aware of many design constraints and this question has no right answer anyway. What I was more interested in is to explore where Go do or doesn’t fail to provide the same experience as a Dart in Flutter.
This thought experiment demonstrated that the major issue Go has is purely syntactical. Inability to call a function and pass either named parameters or untyped literals made it a bit harder and verbose to achieve clean and well-structured DSL-like widget tree creation. There are actually Go proposals to add named parameters in a future Go versions, and it’s probably a backwards-compatible change. Having named parameters would definitely help for UI frameworks in Go, but it also introduces yet another thing to learn, yet another choice to make on each function definition or invocation, so the cumulative benefit is unclear.
There obviously were no issues with the absence of user-defined generics in Go or lack of exceptions. I would be happy to hear about another way to achieve cleaner and more readable Go implementation of Flutter with generics – I’m genuinely curious how it would help here. Feel free to post your thoughts and code in comments.
Thoughts about Flutter future
My final thoughts are that Flutter is unspeakably awesome despite all shortcomings I ranted in this post today. The “awesomeness/meh” ratio is surprisingly high in Flutter and Dart is actually quite easy to learn (if you know other programming languages). Taking into account web pedigree of Dart, I dream about a day, when every browser ships with a fast and optimized Dart VM inside and Flutter can natively serve as a framework for web apps as well (keeping an eye on HummingBird project, but native browser support would be better anyway).
Amount of incredible work done to make Flutter reality is just insane. It’s the project of a quality you dream of and seems to have a great and growing community. At least, the number of extremely well-prepared tutorials is staggering, and I hope to contribute to this awesome project one day.
To me, it’s definitely a game changer, and I’m committed to learn it to its full extent and be able to make great mobile apps every now and then. I encourage you to try Flutter even if you never thought you might be developing a mobile app – it’s really a breath of fresh air.
via divan's blog https://divan.dev
March 14, 2019 at 04:45PM