Hacker News new | ask | show | jobs
by tekkk 1233 days ago
To me as someone who recently got into Flutter to make a side-project (mobile game) the most glaring problem isn't the lack of ecosystem or little UI issues the author mentions but the language itself. You had a green field to make the best programming language for creating UIs and you made Dart? Like what.

I don't have particular issue with the heavy OOP paradigm but the fact you have to write so much boilerplate feeling code (Center here, Container there) you'd imagine you could have simplified it a lot more. The documentation itself also seems like it could be improved. And some little things here and there, like the constructor syntax. I dislike semicolons as well, to me they're just busy work.

Idk, you'd think an org like Google had the resources and the talent to make something truly amazing. Guess it just shows money can't buy everything. The tooling however is quite amazing. Being able to make cross-platform apps so simply is pretty awesome (React native was a pain in the butt the last time I tried it).

8 comments

They've answered why Dart many times. It's a language that fits their needs, since it has JIT, AOT, compilation to JS and now WASM. Back then there weren't many languages that could do all that, and even today there aren't many. Kotlin (and other JVM languages) and TypeScript (and other compile-to-JS-only languages) are already out since they are not AOT, only JIT even now.

More importantly, it was a Google language that they could mold for their own needs, not so possible if it's an outside language like TypeScript. For example, early on, they asked the Dart team to create an AOT compiler since Apple does not allow JITted code apparently (not sure how React Native gets around this then), or maybe it didn't back then, and the Dart team was able to do it successfully for the Flutter team. Try asking the TypeScript team to do the same, it's next to impossible.

For what it's worth, Dart 3+ introduces more functional parts like records, patterns, and exhaustive pattern matching so you can use it in a functional style if you want once that ships. There is also fpdart, for full functional support, and for brevity of code, since Dart has the ability to generate code via macros (with build_runner), you can also use packages like functional_widget, flutter_hooks etc to achieve a very functional code style as well, React-like.

This is a great podcast/video episode on how Flutter came to be, from the early days to now, and how it used JS in the beginning but it didn't serve their needs, as well as how it used to be more imperative until a declarative React-like model was then made.

https://www.youtube.com/watch?v=xqGAC5QCYuQ

> since Apple does not allow JITted code apparently (not sure how React Native gets around this then)

With React Native you have two options. Use Apple's own JavaScriptCore engine which has special JIT permissions, or use Meta's Hermes engine which runs as a bytecode interpreter.

In either case you can always drop down to native code (Swift/Objective-C (iOS), Kotlin/Java (android), or C/C++ (any platform)) which can be used for anything compute heavy.

No, the ability to JIT is not per framework (I'm unsure how that would work?), it's per process. Using JSC doesn't give you a JIT - now the JSC interpreter is very fast, so it's possible claims that they "need JIT" are incorrect (back when I worked on JSC it was a common belief from people that the JITs were magic fairy dust, and so would come in to anything with a data-less assumption that a JIT was needed), it's also possible that "react native" is using wkwebview in which case the content would be running in a separate web content process with a jit.
I don't think React Native is using WkWebView. But that the introduction of WkWebView which allows JIT was the source of my confusion. I was under the impression that JIT was enabled for any uses of JSC in iOS at that time. But it seems that is not the case.

So the situation with React Native is that you can either use JSC, Hermes (or I believe V8 in jitless mode). All of which are interpreters. On Android you can of course use JIT (with either JSC or V8).

> Kotlin (and other JVM languages) and TypeScript (and other compile-to-JS-only languages) are already out since they are not AOT, only JIT even now.

There is Kotlin Native now.

Sure, not ten years ago though when it was first starting. Doesn't Jetpack Compose work similarly to Flutter in that it draws every pixel? If so, Kotlin native could be used there to bring it to web and desktop perhaps.
Java fits all the bills and Google already has ample experience/tooling working with it, including a very great java to js compiler.
Java, again, is not ahead of time compiled, or at least did not have a good solution for that ten years ago. Even Android apps now run as AOT compiled apps with Android Runtime (ART), discontinuing the Dalvik VM.
Java had AoT compilers for more than 20 years, https://en.wikipedia.org/wiki/Excelsior_JET

If Google really wanted they could have wrote one.

The fact that Excelsior is dead probably says something.

There are AoT compilers for Java, but I'm not aware of any successful widely-used ones. Java and the Java ecosystem rely pretty heavily on class loaders and reflection which make AoT compilation very difficult. Java in its bones is designed to be a JIT VM language and you'll always be going against the grain if you try to statically compile it.

Dart was also initially designed to be a JIT VM language (by the same folks who made the HotSpot JVM) but without many of the pitfalls in Java that make AoT hard, and over time we have deliberately evolved the language (in breaking ways!) to make it much more amenable to static compilation.

Languages are not all interchangeable and some languages are better suited for certain implementation strategies than others.

GraalVM native is quite a big thing nowadays, and reflection is not really a problem for AOT, class loading is more so, but that is easily solvable by assuming a closed world (the way Graal does it). So you just simply list all the classes you plan to reflect on/load and that’s it.

> Languages are not all interchangeable and some languages are better suited for certain implementation strategies than others

Sure, but I don’t claim to replace SQL or Prolog with Java, but a regular old managed language which has zero unique features that would give it a reason to exist.

> If Google really wanted they could have wrote one.

Which they did, but for Dart instead.

Which has zero ecosystem and just reinventing the wheel.
I’m not sure why the Dart hate honestly. It’s a pretty solid language in my opinion and I like the direction they’re taking (non-nullable, record, pattern matching). The ecosystem is weak for sure, but that’s not a gripe on the language itself.

I’m curious what do you dislike about it apart from semi-colons and the constructor syntax (which seem fine to me?).

Okay I guess I should provide more details. Constructor syntax fine? Jesus how come you can't use the constructor parameters as the default values but have to either resort to using `late` or that weird `: {}` after constructor like in C++, can't remember how it was. I just added `late` and thought whatever. Probably there are nuances I've missed but how come you had to reinvent that - thought it should be intuitive from the get-go.

While stating itself to be statically typed you can also shoot yourself in the foot by simply using `as Type`, similar to TypeScript. Because that just assigns it to a type and doesn't cast it like you do it in say Rust.

The extreme use of classes with `abstract class` and whatnot feels kinda 90's to me, just a lot of abstractions and for what? Just feels unnecessarily complicated for something seemingly simple.

Maybe it's just little too enterprisey for my taste. Maybe that's it. I'm glad they are taking steps to improve it, I think making it more functional would help it. Even just to distinguish it from the other OOP languages.

And how come simple state management seemed so difficult as well? I tried using my tried and true MobX which is implemented for Dart as well but was disappointed in its complexity and the fact it requires you to run a code generator to wrap your classes with observables. What? Insane.

> Jesus how come you can't use the constructor parameters as the default values but have to either resort to using `late` or that weird `: {}` after constructor like in C++, can't remember how it was.

This is a really good question. The reason is that it ensures that you can never see a field before it's initialized. In Java, you might think that you'll never observe a final field before it's been initialized but not so! In the constructor, you can call a method on `this` even before all fields have been definitely initialized. Inside that method (which might be overridden!), you can then read the field. It will be default initialized.

This means every time you create an object in Java, some extra code is running to default initialize all the fields just in case they get read before they're actually initialized. (I assume in some cases the compiler can prove it's not needed and eliminate it, but not in the general case).

It also means that the type system can't rely on fields being initialized for static safety. That in turn means that the compiler can't optimize based on that fact.

In Dart with the constructor initialization syntax, it's not syntactically possible to access any state on a new instance until after every single one of its initializers has run. That means that, in concert with null safety, if you have a non-nullable field, the compiler knows it will never ever be null and then can generate smaller, faster code based on that guarantee.

I agree the constructor initializer syntax is annoying and I wish we had something better, but it's there for a reason. Also, in practice, you often use `this.` or `super.` on the constructor parameters and avoid the initializers entirely.

> While stating itself to be statically typed you can also shoot yourself in the foot by simply using `as Type`, similar to TypeScript. Because that just assigns it to a type and doesn't cast it like you do it in say Rust.

An "as" expression is fully sound and will throw a runtime exception if the value isn't a valid instance of the cast type.

> The extreme use of classes with `abstract class` and whatnot feels kinda 90's to me, just a lot of abstractions and for what?

You don't have to use classes if you don't want to. You can define functions and variables all at the top level and write in a completely procedural or functional style if that's your jam. Some of the Dart packages I maintain are mostly functions.

> I think making it more functional would help it.

We're getting there. It's always had anonymous functions, closures, and plenty of higher-order functions in the collection API. We're adding pattern mataching and exhaustiveness checking now which should let you program in an algebraic datatype style.

Thanks for the detailed write up!
Exactly, after JVM world I'm especially loving reified generics. Being able to access type information on generics at runtime is very helpful. Plus developer experience for working with DartVM and Flutter is crazy good.
It gets the hate because it comes from Google and it was originally marketed as a JavaScript replacement.

Additionally, the first unoptimized compiler resulted in "hello world" having a gargantuan amount of JavaScript code. That left the language with a hard to shake stigma.

To be fair, around a decade ago, Chrome wanted to add a Dart VM (Dartium) to which many vehemently protested, and I agree it would have been a bad move to have a browser essentially have its own language rather than JS, forcing other browsers to adopt it. From there though, we got a much better cross browser standards-based solution in the form of WASM, which is even more powerful since languages other than Dart could be used. This controversy is where I first saw the Dart hate, but it has grown into a nice language since, especially with Dart 3+ which introduces, records, patterns, and exhaustive pattern matching.
Oh yeah I do remember that. I can see how some of that stigma lingers.
Last I checked you can’t even have enums with payloads/ associated values.

Garbage language

You can since 2.17. Not sure if you’re being sarcastic about this making it a garbage language.
Is that not just string/int/basic types? Last time I looked it was and it was a complete joke.

I want to define

enum UserState {

case loggedOut

case loggedIn(user: User)

}

where User is itself a struct with props like email/id/Etc

Then, I want to be able to switch on said enum, so that my code can take a UserSession state stream and switch over each case to react accordingly. This gives me compiler - enforced case handling completeness everywhere my enum is consumed which is amazing for code reliability.

This is trivial in Swift or any real language, but with Dart and the stupid flutter bloc model you seemingly can’t do this and end up with these huge if cast chains to send messages.

If I’m wrong and things have changed for the better please let me know, it was my biggest issue with dart/flutter

See my sibling comment. That appears to only work with basic types like strings/ints?

I need to add complex objects, sometimes multiple. Swift lets you do this easily.

No, you can add as many fields of whatever types you want to an enum in Dart.
I think you give Dart too little credit. Consider the other popular languages of today - Python, JavaScript, C++. Dart is far nicer than those. I'd say it is nicer than Go too.

I'm not sure what you mean about boilerplate. The way you create UIs in Dart is one of the main draws of Flutter. Would you rather Android's XML? Or QWidget?

The language design is not an issue with Flutter. Flutter's issues are mainly ecosystem size and performance.

Declarative UI has arrived on Android via Jetpack Compose. So there is no need to create new UI in XML anymore.

The abiliy to share the codebase between platforms is much bigger draw rather then the way that the UI is written.

> (Center here, Container there)

Usually, it's a sign that you refactor something to a separate StatelessWidget or at least a function.

If you're coming from web dev, you will be used to having all this container, padding, center, column code in CSS. Now you have it in UI. But on the flip side, this has its flexibility. You can usually refactor the styling part out of the actual widget if you want.

For me the big problems with flutter are state management and platform interop. Yet it's nicer than most other options for developing UI.

They didn’t create Dart for UI. It was dead and they shoehorned it into Flutter.
To be fair it’s not a bad choice for UI: supporting both AOT and JIT speeds up the dev loop and allows compiled performance + hot reloading in dev, which is good for UI development.
Considering that Dart was made to run in browsers, they did in fact.

The APIs the OP are complaining about are Flutter's not Dart's. Flutter tried to do a vdom-like thing that also included styling on top of regular function call syntax and it is... what it is.

The style of writing the UI code takes some time to get used to, but the composability is really nice. For example, if I want to center something, I use Center and it works as I predicted. If I want to change the opacity of something, I don't need to find the opacity property of a particular widget, I can just...use Opacity, and it'll work for everything.

This style is a way to have (extreme) composition over inheritance, which apparently was very useful for the framework authors who mentioned that they didn't need to keep reimplementing opacity for example for every single widget.

Yeah it was an attempt to fix JS before JS improved a lot in recent years, and before TS took off.
> so much boilerplate feeling code (Center here, Container there)

That's Flutter, the framework. I like Dart and I wish some conventional imperative Qt/Delphi-like GUI toolkit was available for it.

You can use Flutter as an imperative framework. Just ignore widgets (the reactive top layer of Flutter) and use the underlying imperative elements directly.
Well, I'd say the main value proposition of Qt/Delphi/wxWidgets/WinForms/etc... is a rich set of widgets/controls including its event handling and layout mechanisms. Is such stuff available in the imperative impl of Flutter?
Center here, Container there

Is that really determined by Dart and not by how Flutter chooses to do things?

No you're right, it's Flutter, they are misconstruing Dart and Flutter.
True, just have a look at Stack Overflow questions tagged Dart, very few (being extra cautious here) are not flutter question.

https://stackoverflow.com/questions/tagged/dart

To be fair, looking at Kotlin would likely yield similar results, being mainly used for Android.
> Idk, you'd think an org like Google had the resources and the talent to make something truly amazing

One would think that, which is why I struggle to believe Google is really invested in Flutter's success. They keep it alive and talk about it, but I wonder how much they actually care about it.