1. Troublesome code generation
There are several problems here. The first one is that you NEED code generation. From having data classes and using state management(like MobX) to effectively implement tons of code for network layer you need to generate code. If you skip the generation part you need to write tons of code. If you want to avoid writing this code manually you have to deal with the code generators which are cumbersome and immature. Don't take my word, let me show you the proof.
Let's say we want to generate the code for an http api client. That's not an easy thing by itself. First of all, I am not aware of any build_runner plugins to do that. So you need to go and download the openapi generator in a form of a .jar file. Then this plugin generates the whole module for you. You are going to be surprised now: the generated code contains errors!
For example, if you're using dio as a network library it will populate the files with 'package:openapi/...' imports which is incorrect! You should not use package imports inside the module and you can't properly alter the module name. Next, openapi generator uses built_value for serialization for dio. And this is a separate problem we're covering later.
Ok, you fixed the problems and now the code compiles and even works. You need to commit the generated code, however you may not want it. Now the real trouble comes in.
The API changes.
So you need to regenerate the whole thing to a new place and manually replace it. What a mess.
But that's not everything! If you're integrating with several services or just having multiple yaml files you need to have several modules. And here we face the next problem...
2. Suboptimal support for the modules
If you're building your application properly, you need to use modules(Or packages/plugins, like they call in Dart/Flutter world). They provide a proper separation of concerns, allows to run the subsets of tests related only to a piece of functionality and provides an ability to move a module to a separate library if it is required.
Or let's say you want to run all the tests for all of your modules. Does 'flutter test' run the tests for the submodules? Unfortunately, no. You need to go to each module separately and run tests there. That means that you need to combine tests results and do something else.
3. Serialization
Ok, my favorite part. Dart does not have a JSON support you would expect it to have in 2020. You can do everything manually with json.decode/json.encode, but you need to provide the serialization logic for each object you have, which sounds like a ton of work. Sure, you can generate the code for it, but code generation sucks in Flutter, as we figured that out couple of points before.
There is another mechanism for serialization: it's built_value/built_collection, but guess what it requires to operate... Right, code generation. And it's buggy again! I faced wierd errors with serializators generated by built_value, and I had to worked around it.
Basically, the general idea how to work with JSON is represented here. You may find the following at least dissapointing:
4. Several Application Schemes support
I bet your app has several endpoints to talk to: like dev, stagin and production environment. Chances are you also have several profiles for firebase, or may be different features enabled. The typical solution is having different targets and schemes like I wrote here. The bad news is that it is not officially supported by Flutter framework and you need to introduce some manual script to do that. Bravo, Flutter, very mature. Of course, there are workarounds, but they are exactly that: workarounds.
5. Poor official tooling
Dart is called a statically typed language once again. One of the advantage of such languages is that the tooling support is easy to build: the IDE knows the exact type of the variable and can show you what method is available, what value can you use and so on, like it happens for Java or Kotlin in Android Studio/IntelliJ Idea. I am pretty used to it during my Java backend development or native Android and I really thought I would get that for dart as well.
The problem here is that it simply doesn't work. Pressing Ctrl + Space + Space simply doesn't give anything. I filed a ticket for this support, please upvote it.
Another issue here is that context actions are working poorly. Say, we have an inspection telling that the method which should call super is not doing it(btw, this is a great annotation @mustCallSuper). Then why on Earth the IDE is not doing it automatically by the Alt+Enter? Another ticket filed.
You may object and say those are the issues of a particular IDE, but this is not true. Those are the limitations of the Dart SDK itself and can not be resolved without the appropriate support from the language itself.
There are other problems, like late support for new Android Studio/IntelliJ Idea versions, Dart plugin issues with actions bar not dissapearing and others.
6. Incomplete static typing
Dart is called a statically typed language. It also has this dynamic keyword which basically mean, not so statically. I know this depends on the person who is writing code and the code reviewer, and maybe some tools to limit it's usage, but the problem is that it is too easy to abuse.
However, let's take a look on some snippets. Let's say you're declaring a very simple function omitting the return value:
hello() {
return 3;
}
Then we want to call it. Ok, no problem:
test("Hello", () {
var hel = hello();
hel.toDouble();
});
So, where's your static typing? Is it enforced by anything? Does flutter analyze give any warnings? There is nothing. IDE autocomplete doesn't work as well and no wonder. However, you can configure it though through custom rules or use Pedantic Dart style. It means that you need to put additional effort to make actual use of static typing.
7. Poor media capabilities
If you're building an app which uses a lot of media like videos and audios you would be surprised how limited the support is. Let's say you want to have a video played back. You're googling and find out there's a video_player package. It is capable of showing the videos by integrating with ExoPlayer and AVPlayer for Android and iOS specifically, but that basically it. Custom controls? Build by yourself! Subtitles? Only SRT, mate. Video speed button? Forget it.
Ok, let's talk about sound. Why audio recorder plugin requires Android 7.0 and above? I don't know.
8. Community Support
Ok, the framework is young, but you're told the community will help. Yeah. What about Storybooks? You've got a package called storyboard, however it lacks crucial customization and the pull request I submitted is hanging there for days. The video speed button I mentioned above actually is supported with the pull request, but it is not merged for more than a year.
I also personally struggle to find a qualified help in the community for the problems I mentioned above. Eventually I found a collegue who helped, but you know, that wasn't easy. Maybe, I haven't looked in the right place? Reach me on twitter and correct me!
9. Testing Support
I know I mentioned it before in the Modules support section, however let's iterate it again.
First, you need 2 separate libraries for pure dart tests and flutter tests and they are not intercompatible. So 'flutter test' doesn't run the tests written with dart test package. You need a separate command for it like 'flutter pub run test'.
Second, you can't run the tests from the submodules from your IDE(from Android Studio/IntelliJ Idea). It just refuses to do so with some weird error!
Third, 'flutter test' doesn't run any tests inside the submodules and you need to do that manually.
Fourth, to get a test coverage you need a separate package and a list of steps which should automated both locally and on your CI. Compare it to jest from JS world which gives you it out of the box. Another sign of immaturity here.
10. Double class hierarchy for the same thing
So, you have a StatelessWidget to display a portion of UI which is a pure function of it's parameters. And that's great! However, when you need some state management inside the widget, you need another class, a StatefulWidget. Wait, and another one! You need a State as well. Moreover, your state should be binded to the widget by it's type parameter. This sounds like not a very well thought decision. Why you can't just have a Widget? I don't know.
I would also go further: why you need a class in a first place? JetpackCompose and React are using only functions nowadays, and that's great. It is possible because both frameworks are basically using hooks. You can use hooks in Flutter as well, but I don't know anybody who does.
So, instead of having just functions for your UI you're left with 3 classes and 2 class hierarchies for mere rendering.
Conclusion
Are those all the problems? I believe not. However, despite Flutter is a very perspective technology which can be used in production(like hundreds of projects already including those I am working on right now), there will be situations when the framework and language immaturity can be a severe risk which should be at least carefully considered.
If you liked the article or have any objections/thoughts do not hesitate to reach me out on Twitter and Telegram, or drop me an email at vladimir.vs.ivanov[ at ]gmail.com and of course, make sure to subscribe here to know about new posts, it's free!