Flutter has established itself as a major player in the cross-platform space. I've been working on a Flutter app for the last 30 days, and here are a few things I've learned.
Keyboard shortcuts are great for speeding up productivity, and I tend to spam my "format code" keybind When I first started using Flutter, I was baffled about why sometimes my code would clean itself up, and other times it would remain a tangled mess. The Flutter dart formatter only works when you use trailing commas!
Widgets are better without state. There are definitely scenarios where you will need to use a StatefulWidget, but use stateless widgets wherever possible! Handling state in other ways (see the "state" tip below) will make your code more maintainable in the future. Using factories can help with setting up stateless widgets. I use Google's Firestore, and providing a factory is a great way to map your data to a strongly typed object and keeping your widget stateless.
Dart exposes all of the available classes from the import, which comes at great convenience when you are not familiar with the exact name of the class you need, but can cause some overlap causing the compiler not to understand which classes you want to use. The application I'm working on uses many generic terms/classes, often causing these overlaps and causing obscure errors if you are unfamiliar with Flutter.
There are a few ways to avoid this situation. The first and my favorite is to hide the conflicting class.
You can also specify all of the classes that you need from the import, which normally sounds favorable to me, but in this case, it can get a little verbose.
Lastly, if you actually need both classes in the same place, you will have to provide a prefix/alias to access what you want.
When I ran the starter app, the first thing I wanted to do was remove the ugly debug banner from the Material banner. You can add the debugShowCheckedModeBanner flag to your material app.
Handling asynchronous actions is one of the most important things in any framework because every app connects to something somewhere. Coming from Typescript, I'm more familiar with the terms promise and observable, but the best ways to handle futures and streams in flutter is with FutureBuilder and StreamBuilder. These widgets take two main parameters, the call loading the data and a builder, which acts as the standard build method on StatelessWidget that updates when the request completes.
Since my application does not need any fancy loading animations, I decided to set up a global loading widget inside a snapshot resolver class, so I'm not repeating the same if/else combination throughout my app.
If your app needs more complicated custom animations depending on what is loading, then this resolver class is not for you, but the implementation would look the same, checking if the snapshot has data, an error, or otherwise return your loading widget.
The first time I tried flutter (early 2019), I gave up on it quickly simply because I was not too fond of the nesting widgets structure. If you are not careful, you will end up with what I call triangle code
After revisiting it more recently, I've realized that your widgets should never really reach extremes like shown above. Extracting out widgets into their own custom reusable classes can clean up your code significantly! Flutter's tooling makes this super easy with "Extract Widget."
This sounds pretty straightforward, but consider it as a reminder. I was tempted to stick with testing on the Flutter Web deploy while writing a bulk of my code because it's easier on my laptop to run a browser than an emulator or dangle a phone from a cable, but even the base material components have quirks on different systems. Something as simple as a scrollbar worked on Android and the web but was not positioned correctly on iOS (see the SafeArea below).
State management is always a big debate for any framework that doesn't directly set a standard for itself, and Flutter is no different. There are countless helper libraries out there, but getIt is the one that I found to be the most straightforward transitioning from an Angular web environment (for small apps). getIt allows you to set up singleton services that can then inject them throughout your application, similar to how Angular lets you inject singleton services through a component's constructor.
Once your services are set up, you can then access them anywhere in your application like this:
With phones being released in all different shapes and sizes, it can be difficult to make sure your app will work as expected on all devices. One way to combat some of these issues is with the "SafeArea" widget. I was experiencing some issues with the scrollbar in my application on iOS devices because the calculation for the position was not taking into account the notch (a bug issue has been filed on Github and will be fixed in a future release), but wrapping my widget in a SafeArea resolved the issue. When in doubt, use the SafeArea.
Most services providing data will be serving your app JSON, so we need to know how to handle taking that JSON and creating a strongly typed object. Flutter provides a package for translating JSON using json.decode(object) into a map of strings to dynamic objects. From there, you will want to create a factory that translates that map into a strongly typed object.
In my case, I'm getting my data from Google's Firestore, so I took it one step further to implement a factory that takes a Firestore DocumentSnapshot:
One bad habit I brought with me from Typescript and Angular is creating a class (interface) for my JSON object and then a separate widget class for displaying/building the object on the screen. If your object is directly represented by widgets on the screen, you can go ahead and have your strongly typed object coming from JSON extend StatelessWidget!
You will obviously want/need to install the Dart language service and Flutter extensions, but there are a few other extensions that I've found really helpful while working on flutter.
Bracket Pair Colorizer 2 is beneficial for keeping track of what widget you are nested in. (If you use IntelliJ instead of VSCode, there is a "Rainbow Brackets" plugin that works similarly)
Here are some settings that I use in VSCode to turn down the rainbow explosion. This will match the default VSCode theme pretty closely:
"bracket-pair-colorizer-2.colors": , "bracket-pair-colorizer-2.scopeLineCSS": [ "borderStyle : solid", "borderWidth : 2px", "borderColor : #6e5880", "opacity: 1" ]
Indent Rainbow accomplishes a similar goal to Bracket Pair Colorizer 2, but it allows you to keep track of your indentation.
This is available in both VSCode and Intellij. Here are some settings again to turn down the rainbows. I use opacity here, which should help this look pretty nice with most themes.
"indentRainbow.colors": [ "rgba(50,50,50,1.00)", "rgba(50,50,50,0.75)", "rgba(50,50,50,0.50)", "rgba(50,50,50,0.25)", "rgba(50,50,50,0.00)" ],
This is a recurring theme here, but this is another extension to help you keep track of what widget you are working in. This one will highlight the scope of the current code block. (I'm not sure there is an equivalent for IntelliJ on this one, but I think you can accomplish something similar with settings)
Overall I'm very impressed with how far Flutter has come. After spending 30+ days working on an app sparingly after hours, as an Angular developer, I could pick it up and be effective quickly. Hopefully, you found some of these tips helpful, and follow for more!