Most recently, I discovered Flutter - a new framework from Google for developing cross-platform mobile applications - and even had the opportunity to show the basics of Flutter to a person who had never programmed before. Flutter itself is written in Dart, a language that was born in the Chrome browser and escaped into the console world - and this made me think "hmm, and Flutter could well have been written on Go!".
Why not? Both Go and Dart are created by Google, both typed compiled languages ​​- turn some events a little differently, Go would be a great candidate for a large-scale project like Flutter. Someone will say - there are no classes, generics and exceptions in Go, so it does not fit.
So let's imagine that Flutter is already written on Go. How will the code look and in general, will it work out?
I have been following this language since its inception as an alternative to JavaScript in browsers. Dart was built into the Chrome browser for a while and the hope was that it would force out JS. It was really sad to read in March 2015 that Dart support was removed from Chrome .
Dart himself is great! Well, basically, after JavaScript, any language is great, but after, say, Go, Dart is not so beautiful. but quite ok. It has all imaginable and unimaginable features - classes, generics, exceptions, futures, async-await, event loop, JIT / AOT, garbage collection, function overload - name any known feature from the theory of programming languages ​​and in Dart it will be with a high proportion probabilities. Dart has a special syntax for almost any piece - a special syntax for getters / setters, a special syntax for abbreviated constructors, a special syntax for special syntax, and much more.
This makes Dart right at a glance familiar to people who have already programmed in any programming language before, and that’s great. But trying to explain all this abundance of special features in the simple "Hello, world" example, I found that this, on the contrary, makes it difficult to master.
In principle, this trinity - “special”, “hidden” and “ambiguous” - not bad captures the essence of what people call “magic” in programming languages. These are features created to simplify the writing of code, but in fact complicating its reading and understanding.
And this is exactly the area where Go takes a fundamentally different position from other languages, and fiercely holds its defense. Go is a language practically without magic - the amount of "hidden", "special" and "ambiguous" in it is reduced to a minimum. But Go has its drawbacks.
Since we are talking about Flutter, and this is a UI framework, let's consider Go as a tool for describing and working with UI. In general, UI frameworks are a colossal task and almost always require specialized solutions. One of the most frequent approaches in the UI is the creation of DSL - domain-specific languages ​​- implemented in the form of libraries or frameworks, sharpened specifically for the needs of the UI. And most often you can hear the opinion that Go is objectively a bad language for DSL.
In essence, DSL means creating a new language — terms and verbs — that a developer can operate on. The code on it should clearly describe the main features of the graphical interface and its components, be flexible enough to unleash the designer's imagination, and still be tough enough to restrict it in accordance with certain rules. For example, you should be able to place the buttons on some container, and place the icon in the right place in this button, but the compiler should return an error if you try to insert a button in, say, text.
Plus, languages ​​for describing UI are often declarative - giving the opportunity to describe the interface in the form of "what I would like to see", and let the framework understand from this what code and how to run.
Some languages ​​were originally developed with such tasks on the sights, but not Go. It seems that writing Flutter on Go will be the one more task!
If you are not familiar with Flutter yet, then I strongly recommend spending the next weekend watching educational videos or reading tutorials that are many. Because Flutter, without any doubt, overturns the rules of the game in the development of mobile applications. And, quite likely, not only mobile - there are already renderers (in Flutter terms, embedders) in order to run Flutter applications as native dekstop applications , and as web applications .
It is easy to learn, it is logical, it comes with a huge library of beautiful widgets on Material Design (and not only), it has a great and large community and an excellent tuling (if you like the ease of working with go build/run/test
in Go, then in Flutter you will get a similar experience).
A year ago I needed to write a small mobile application (for iOS and Android, of course), and I understood that the complexity of developing a high-quality application for both platforms was too great (the application was not the main task) - I had to outsource and pay for it. In fact, writing a simple, but high-quality application running on all devices was an impossible task, even for a person with almost 20 years of programming experience. And it has always been nonsense for me.
With Flutter, I rewrote this application for 3 pm, while learning the framework from scratch. If someone had told me that this could be a little earlier, I would not have believed it.
The last time I saw a similar productivity boost with the discovery of a new technology was 5 years ago when I discovered Go. That moment changed my life.
So I recommend to start learning Flutter and this tutorial is very good .
When you create a new application through flutter create
, you will receive such a program with a title, text, a counter and a button that increments the counter.
I think this is a great example. to write it on our imaginary Flutter on Go. It has almost all the basic concepts of the framework, where you can test the idea. Let's look at the code (this is one file):
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } }
Let's take a look at the code in parts, analyze what and how it falls on Go, and take a look at the various options that we have.
The beginning will be simple and straightforward - import dependencies and start the function main()
. Nothing complicated or interesting here, the change is almost syntactic:
package hello import "github.com/flutter/flutter" func main() { app := NewApp() flutter.Run(app) }
The only difference is that instead of running MyApp()
- a function that is a constructor, which is a special function that is hidden inside a class called MyApp - we simply call the usual explicit and NewApp()
function NewApp()
. It does the same thing, but it is much clearer to explain and understand what it is, how it starts and how it works.
In Flutter, everything consists of widgets. In the Dart version of Flutter, each widget is implemented as a class that inherits special classes for widgets from Flutter.
In Go, there are no classes, and, accordingly, a class hierarchy, because the world is not object-oriented, and certainly not hierarchical. For programmers familiar only with a class-oriented OOP model, this may be a revelation, but it really isn't. The world is a giant interlaced graph of concepts, processes, and interactions. It is not perfectly structured, but not chaotic, and an attempt to squeeze it into class hierarchies is the most reliable way to make the code base unreadable and cumbersome - just what most of the code bases are for the time being.
I really appreciate Go for the fact that its creators bother to rethink this ubiquitous class concept and implemented Go for a much simpler and more powerful OOP concept, which, by chance, turned out to be closer to what the OOP creator, Alan Kay, had in mind .
In Go, we represent any abstraction in the form of a specific type - structure:
type MyApp struct { // ... }
In the Dart version of Flutter, MyApp
should inherit a StatelessWidget
and override the build
method. This is needed to solve two problems:
MyApp
) some special properties / methodsI do not know Flutter internals, so suppose that point number 1 is not in question, and we just have to do it. In Go, for such a task there is a single and obvious solution - embedding the types:
type MyApp struct { flutter.Core // ... }
This code will add all the properties and methods of flutter.Core
to our MyApp
type. I called it Core
instead of Widget
, because, firstly, embedding the type still does not make our MyApp
widget, and, secondly, this name is very well used in the GopherJS framework Vecty (something like React, only for Go). I will touch on the topic of similarity Vecty and Flutter a little later.
The second point - the implementation of the build()
method, which Flutter can use - is also solved in Go easily and unambiguously. We only need to add a method with a specific signature that satisfies an interface defined somewhere in the library of our fictional Flutter on Go:
flutter.go:
type Widget interface { Build(ctx BuildContext) Widget }
And now our main.go:
type MyApp struct { flutter.Core // ... } // Build renders the MyApp widget. Implements Widget interface. func (m *MyApp) Build(ctx flutter.BuildContext) flutter.Widget { return flutter.MaterialApp() }
We can notice a few differences here:
BuildContext
, Widget
and MaterialApp
point to the flutter
import in front of them.extends Widget
or @override
Build()
method begins with a capital letter, because it means the publicity of the method in Go. In Dart, publicity is determined by whether the name begins with an underscore (_) or not.So, to make a widget in our Flutter on Go, we need to embed the flutter.Core
type and implement the flutter.Widget
interface. With this sorted out, dig further.
That was one of the things that confused me a lot in Flutter. There are two different classes - StatelessWidget
and StatefulWidget
. As for me, a “stateless widget” is the same widget, just without, hmm, data, state — why is there a new class here? But ok, I can live with that.
But further - more, you cannot just inherit another class ( StatefulWidget
), but you should write this kind of magic (IDE will do it for you, but not the essence):
class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold() } }
Hmm, let's see what is happening here.
Fundamentally, the task is as follows: add a state to the widget (state), a counter, in our case, and let the Flutter engine know when we changed the state to redraw the widget. This is the real complexity of the problem (essential complexity in terms of Brooks).
All the rest is additional complexity (accidental complexity). Flutter on Dart comes up with a new State
class that uses generics and accepts a widget as a type parameter. Next, create the _MyHomePageState
class, which inherits the State MyApp
... ok, this can still be digested. But why is the build()
method defined by the State class, and not by the class that has the widget? Brrr ....
The answer to this question is in the Flutter FAQ and is described in detail here and the brief answer is to avoid a certain class of bugs when inheriting StatefulWidget
. In other words, this is a workaround to solve the problem of class-oriented OOP design. Chic.
How would we do this in Go?
Firstly, I would personally by all means choose not to create a separate entity for the “state” - State
. After all, we already have a state in each specific type - these are just the fields of the structure. Language has already given us this essence, so to speak. Creating another similar entity will only confuse the programmer.
The task, of course, is to enable Flutter to react to a change in state (this is the essence of reactive programming, after all). And if we can "ask" the developer to use a special function ( setState()
), then in a similar way we can instead ask to use a special function to tell the engine when to redraw and when not. In the end, not all state changes require redrawing, and here we will have even more control:
type MyHomePage struct { flutter.Core counter int } // Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget { return flutter.Scaffold() } // incrementCounter increments widgets's counter by one. func (m *MyHomePage) incrementCounter() { m.counter++ flutter.Rerender(m) // or m.Rerender() // or m.NeedsUpdate() }
You can play NeedsUpdate()
with different naming options - I like NeedsUpdate()
for being direct and because this is a widget property (derived from flutter.Core
), but the global flutter.Rerender()
method also looks good. True, it gives a false sense that the widget is now immediately redrawn, but this is not so - it will be redrawn on the next frame update, and the method call frequency can be much higher than the draw frequency - but our Flutter engine should deal with this.
But the idea is that we just solved the necessary task without adding:
Plus, the API is much clearer and clearer - just increment the counter (as you would in any other program) and ask Flutter to redraw the widget. This is something that is not very obvious if we just called setState
— which is not just a special function for setting the state, but a function that returns a function (wtf?) In which we already do something with the state. Again, hidden magic in languages ​​and frameworks makes it very difficult to understand and read code.
In our case, we solved the same problem, the code is simpler and shorter twice.
As a logical continuation of the theme, let's take a look at how the “state widget” is used in another widget in Flutter:
@override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: MyHomePage(title: 'Flutter Demo Home Page'), ); }
MyHomePage
here is a "state widget" (it has a counter), and we create it by calling the constructor MyHomePage()
during the build ... Wait, what?
build()
is called to redraw the widget, quite possibly many times a second. Why do we have to create a widget, especially with the state, every time during the drawing? This makes no sense.
It turns out that Flutter uses this separation between Widget
and State
to hide this initialization / state management from the programmer (more hidden things, more!). It creates a new widget every time, but the state, if it has already been created, finds it automatically and attaches to the widget. This magic happens invisibly and I have no idea how it works - you need to read the code.
I think this is a real evil in programming - to hide and hide from the programmer as much as possible, justifying it with ergonomics. I am sure that the average programmer will not read the Flutter code in order to understand how this magic works, and it is unlikely that they will understand how and what is interconnected.
For the Go version, I definitely would not want such a hidden witchcraft, and would prefer explicit and visible initialization, even if it means a slightly more unfounded code. Flutter's Dart approach can certainly be implemented, but I love Go for minimizing magic, and I would like to see this philosophy in frameworks. Therefore, my code for widgets with the state in the widget tree I would write like this:
// MyApp is our top application widget. type MyApp struct { flutter.Core homePage *MyHomePage } // NewMyApp instantiates a new MyApp widget func NewMyApp() *MyApp { app := &MyApp{} app.homePage = &MyHomePage{} return app } // Build renders the MyApp widget. Implements Widget interface. func (m *MyApp) Build(ctx flutter.BuildContext) flutter.Widget { return m.homePage } // MyHomePage is a home page widget. type MyHomePage struct { flutter.Core counter int } // Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget { return flutter.Scaffold() } // incrementCounter increments app's counter by one. func (m *MyHomePage) incrementCounter() { m.counter++ flutter.Rerender(m) }
This code loses the Dart version in that if I want to remove the homePage
from the widget tree and replace it with something else, then I will have to clean it in three places, instead of one. But in return, we get a complete picture of what, where and how is happening, where memory is allocated, who causes whom, and so on - the code in the palm of your hand is understandable and easy to read.
By the way, Flutter still has such a thing as StatefulBuilder , which adds even more magic and allows you to make widgets with state on the fly.
Now we take on the most fun part. How are we going to have a widget tree on Go? We want it to look brief, clean, easy in refactoring and changes, describing the spatial relationships between widgets (widgets that are visually close, should be near and in the description), and, at the same time, flexible enough to describe in it an arbitrary code like event handlers.
I think the option on Dart is quite beautiful and eloquent:
return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('You have pushed the button this many times:'), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), );
Each widget has a constructor that takes optional parameters, and what makes the entry really nice here is the named parameters of the functions .
In case you are not familiar with this term, in many languages ​​the parameters of the function are called “positional”, since their position is important for the function:
Foo(arg1, arg2, arg3)
, and in the case of named parameters, everything is decided by their name in the call:
Foo(name: arg1, description: arg2, size: arg3)
This adds text, but saves clicks and movements through the code, in an attempt to understand what the parameters mean.
In the case of the widget tree, they play a key role in readability. Compare the same code as above, but without named parameters:
return Scaffold( AppBar( Text(widget.title), ), Center( Column( MainAxisAlignment.center, <Widget>[ Text('You have pushed the button this many times:'), Text( '$_counter', Theme.of(context).textTheme.display1, ), ], ), ), FloatingActionButton( _incrementCounter, 'Increment', Icon(Icons.add), ), );
Not that. true? Not only is it more difficult to understand (you need to keep in mind what each parameter means and what its type is, and this is a significant cognitive load), but it also does not give us the freedom to choose which parameters we want to transfer. For example, you may not want a FloatingActionButton
for your Material, so you simply do not specify it in the parameters. Without named parameters, we will have to either force all possible widgets to be specified, or resort to magic with reflection to find out exactly which widgets have been transmitted.
And since Go has no overloading of functions and named parameters, this will not be an easy task for Go.
Let's take a closer look at the Scaffold object, which is a convenient wrapper for a mobile application. It has several properties - appBar, drawe, home, bottomNavigationBar, floatingActionBar - and these are all widgets. Creating a tree of widgets, we actually need to somehow initialize this object, passing it the above-mentioned widget properties. Well, this is not too different from the usual creation and initialization of objects.
Let's try the approach "in the forehead":
return flutter.NewScaffold( flutter.NewAppBar( flutter.Text("Flutter Go app", nil), ), nil, nil, flutter.NewCenter( flutter.NewColumn( flutter.MainAxisCenterAlignment, nil, []flutter.Widget{ flutter.Text("You have pushed the button this many times:", nil), flutter.Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1), }, ), ), flutter.FloatingActionButton( flutter.NewIcon(icons.Add), "Increment", m.onPressed, nil, nil, ), )
Not the most beautiful UI code, definitely. The word flutter
everywhere asking for it. to hide it (actually, I had to call the package material
, not flutter
, but not the essence), the nameless parameters are completely unclear, and these nil
s everywhere are completely confusing.
Since anyway most of the code will use one type or function from the flutter
package, we can use the “dot import” format to import the package into our namespace and, thus, hide the package name:
import . "github.com/flutter/flutter"
Now, instead of flutter.Text
we can write just Text
. This is usually a bad practice, but we are working with the framework, and this import will be literally in every line. From my practice, this is exactly the case for which such an import is admissible - for example, as when using a wonderful GoConvey testing framework .
Let's see what the code will look like:
return NewScaffold( NewAppBar( Text("Flutter Go app", nil), ), nil, nil, NewCenter( NewColumn( MainAxisCenterAlignment, nil, []Widget{ Text("You have pushed the button this many times:", nil), Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1), }, ), ), FloatingActionButton( NewIcon(icons.Add), "Increment", m.onPressed, nil, nil, ), )
Already better, but these nil-s and unnamed parameters ....
Let's see what the code will look like if we use reflection (the ability to inspect the code while the program is running) to analyze the passed parameters. This approach is used in several early HTTP frameworks on Go ( martini , for example), and is considered a very bad practice - it is insecure, loses the convenience of the type system, is relatively slow and adds magic to the code - but for the sake of experiment you can try:
return NewScaffold( NewAppBar( Text("Flutter Go app"), ), NewCenter( NewColumn( MainAxisCenterAlignment, []Widget{ Text("You have pushed the button this many times:"), Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1), }, ), ), FloatingActionButton( NewIcon(icons.Add), "Increment", m.onPressed, ), )
Not bad, and it looks like the original version of Dart, but the lack of named parameters still hurts the eye much.
Let's step back a bit and ask ourselves what exactly we are trying to do. We do not need to blindly copy the Dart approach (although this will be a nice bonus - less new to teach people already familiar with Flutter on Dart). In essence, we simply create new objects and assign properties to them.
Maybe try this way?
scaffold := NewScaffold() scaffold.AppBar = NewAppBar(Text("Flutter Go app")) column := NewColumn() column.MainAxisAlignment = MainAxisCenterAlignment counterText := Text(fmt.Sprintf("%d", m.counter)) counterText.Style = ctx.Theme.textTheme.display1 column.Children = []Widget{ Text("You have pushed the button this many times:"), counterText, } center := NewCenter() center.Child = column scaffold.Home = center icon := NewIcon(icons.Add), fab := NewFloatingActionButton() fab.Icon = icon fab.Text = "Increment" fab.Handler = m.onPressed scaffold.FloatingActionButton = fab return scaffold
, " ", . -, – , . -, , .
QGridLayout *layout = new QGridLayout(this); layout->addWidget(new QLabel(tr("Object name:")), 0, 0); layout->addWidget(m_objectName, 0, 1); layout->addWidget(new QLabel(tr("Location:")), 1, 0); m_location->setEditable(false); m_location->addItem(tr("Top")); m_location->addItem(tr("Left")); m_location->addItem(tr("Right")); m_location->addItem(tr("Bottom")); m_location->addItem(tr("Restore")); layout->addWidget(m_location, 1, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); layout->addWidget(buttonBox, 2, 0, 1, 2);
, - . , , , .
, – -. For example:
func Build() Widget { return NewScaffold(ScaffoldParams{ AppBar: NewAppBar(AppBarParams{ Title: Text(TextParams{ Text: "My Home Page", }), }), Body: NewCenter(CenterParams{ Child: NewColumn(ColumnParams{ MainAxisAlignment: MainAxisAlignment.center, Children: []Widget{ Text(TextParams{ Text: "You have pushed the button this many times:", }), Text(TextParams{ Text: fmt.Sprintf("%d", m.counter), Style: ctx.textTheme.display1, }), }, }), }), FloatingActionButton: NewFloatingActionButton( FloatingActionButtonParams{ OnPressed: m.incrementCounter, Tooltip: "Increment", Child: NewIcon(IconParams{ Icon: Icons.add, }), }, ), }) }
! , . ...Params
, . , , Go , , .
-, ...Params
, . (proposal) — " " . , FloatingActionButtonParameters{...}
{...}
. :
func Build() Widget { return NewScaffold({ AppBar: NewAppBar({ Title: Text({ Text: "My Home Page", }), }), Body: NewCenter({ Child: NewColumn({ MainAxisAlignment: MainAxisAlignment.center, Children: []Widget{ Text({ Text: "You have pushed the button this many times:", }), Text({ Text: fmt.Sprintf("%d", m.counter), Style: ctx.textTheme.display1, }), }, }), }), FloatingActionButton: NewFloatingActionButton({ OnPressed: m.incrementCounter, Tooltip: "Increment", Child: NewIcon({ Icon: Icons.add, }), }, ), }) }
Dart! .
, . , , , , .
, , , -, – :
button := NewButton(). WithText("Click me"). WithStyle(MyButtonStyle1)
or
button := NewButton(). Text("Click me"). Style(MyButtonStyle1)
Scaffold- :
// Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget { return NewScaffold(). AppBar(NewAppBar(). Text("Flutter Go app")). Child(NewCenter(). Child(NewColumn(). MainAxisAlignment(MainAxisCenterAlignment). Children([]Widget{ Text("You have pushed the button this many times:"), Text(fmt.Sprintf("%d", m.counter)). Style(ctx.Theme.textTheme.display1), }))). FloatingActionButton(NewFloatingActionButton(). Icon(NewIcon(icons.Add)). Text("Increment"). Handler(m.onPressed)) }
Go – , . Dart-, :
New...()
– , . , — " , , , , , , – " .
, , 5- 6- .
"hello, world" Flutter Go:
main.go
package hello import "github.com/flutter/flutter" func main() { flutter.Run(NewMyApp()) }
app.go:
package hello import . "github.com/flutter/flutter" // MyApp is our top application widget. type MyApp struct { Core homePage *MyHomePage } // NewMyApp instantiates a new MyApp widget func NewMyApp() *MyApp { app := &MyApp{} app.homePage = &MyHomePage{} return app } // Build renders the MyApp widget. Implements Widget interface. func (m *MyApp) Build(ctx BuildContext) Widget { return m.homePage }
home_page.go:
package hello import ( "fmt" . "github.com/flutter/flutter" ) // MyHomePage is a home page widget. type MyHomePage struct { Core counter int } // Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx BuildContext) Widget { return NewScaffold(ScaffoldParams{ AppBar: NewAppBar(AppBarParams{ Title: Text(TextParams{ Text: "My Home Page", }), }), Body: NewCenter(CenterParams{ Child: NewColumn(ColumnParams{ MainAxisAlignment: MainAxisAlignment.center, Children: []Widget{ Text(TextParams{ Text: "You have pushed the button this many times:", }), Text(TextParams{ Text: fmt.Sprintf("%d", m.counter), Style: ctx.textTheme.display1, }), }, }), }), FloatingActionButton: NewFloatingActionButton( FloatingActionButtonParameters{ OnPressed: m.incrementCounter, Tooltip: "Increment", Child: NewIcon(IconParams{ Icon: Icons.add, }), }, ), }) } // incrementCounter increments app's counter by one. func (m *MyHomePage) incrementCounter() { m.counter++ flutter.Rerender(m) }
!
, , Vecty . , , , , Vecty DOM/CSS/JS, Flutter , 120 . , Vecty , Flutter Go Vecty .
– , . Flutter, .
" Flutter Go?" "" , , , , , Flutter, , , "" . , Go .
Go. – , .
, Flutter , , . "/ " , Dart ( , , ). Dart, , (, ) DartVM V8, Flutter – Flutter -.
, . . , , 1.0 . , - .
game changer, Flutter , , .
UI – Flutter, .
Source: https://habr.com/ru/post/435960/
All Articles