A developer checklist

I noticed that when we build an app with Flutter there is a common set of tasks that we are encountering:

Theming Changing Screens Form Card Layout

In this article, we will go through all these, describing what they do, how to use them, sharing little tricks / templates which I found out by trial and error, so that next time when you build your app you don’t have to repeat all these.

1. Theming

Choosing the primary and accent colours for the whole app. Easy it might seem, esp. if you have worked with the material design component library for web previously, the tremendous number of properties in ThemeData( ) will make you confused. For example, you may not be sure whether you should set the primaryColor or primarySwatch properties, or both. And why. Or what can brightness do to other colours. After a little bit of trial and error, and I found that in most cases we can simply set the following properties:

brightness — this property has the highest “priority” when colouring an app. If it is set to Brightness.dark, background colour of “surfaces” such as Scaffold , Card , Modal/Standard Bottom Sheet will be in black/grey/etc.

, , Modal/Standard Bottom Sheet will be in black/grey/etc. primarySwatch — it is a “spectrum” of colours. For example, if we choose Colors.purple, it will be a set of “different purples” of various intensity. For use cases, this will be applied to background colour of AppBar / UserDrawerHeader, selected colour of ListTile, etc. Higher intensity (deeper) ones will be applied to places such as the device’s status bar at the top in Android, background colour of CircleAvatar if its child is of Icon class. Lower intensity (paler) ones will be applied to for example ripple colour on button tap, ripple colour on ListView scrolling ends.You may notice that the type of this property of “MaterialColor”. Using our example of setting primarySwatch: Colors.purple, Flutter will pick the set of purples from 2014 Material Design color palettes (Scroll to the bottom of that page to see the palette). It is suggested not to use Color with name ends with “accent” for primarySwatch/primaryColor.

// For a list of available primary colours in Flutter, try print(Colors.primaries);

primaryColor — If this is null, Flutter will give the default of intensity. Using the above example that would be Colors.purple[500]. You can use give something like Colors.purple[600] so that your AppBar will be in a deeper purple. However, there is a gotcha. If you leave it as null, then when the app is in dark mode (brightness: Brightness.dark), your purple color will not be applied to the app but instead fallback to Flutter’s blue. So it is advisable to put Colors.purple at this property even though it seems to be duplicated with the primarySwatch property above.

primaryColor set to null (left) vs non-null (right)

accentColor — This will be applied to FloatingActionButton colour, border bottom colour of the selected Tab, checked Checkbox, selected Radio, turned-on Switch, confirm/cancel buttons in a Dialog, selected date in Date Picker, etc. Feel free to try any Colors, with or without name ended with “accent”.

// For a list of available accent colours in Flutter, try print(Colors.accents);

Applications of accent colour

Properties brightness, primarySwatch, primaryColor, and accentColor could probably do the job. However there are still 2 corner cases that you will want to handle.

buttonTheme — background colour of RasiedButton / text colour of FlatButton will not follow the primary colour you have given above. Below can do the trick to fix.

buttonTheme: ButtonThemeData(textTheme: ButtonTextTheme.primary)

toggleableActiveColor —if this property is null, then in dark mode, checked Checkbox, selected Radio, turned-on Switch will have not have the accent colour you have chosen above. So you may want to set this property to be the same is that of accentColor.

accentColor non-null (left) vs null (right) in dark mode

To conclude, choose 2 colours (primary and accent), apply them to a minimal set of properties of ThemeData( ) in the build method of the app’s root widget, you are good to go.

Minimal set of properties for ThemeData

2. Changing Screens

No, not Navigation. I am talking about Drawer, Tabs, Bottom Navigation Bar. They do not push or pop the navigation stack. They themselves keep track of a “current index”, and change the body by setState, when user tabs a drawer tile / swipes tabs / tabs a bottom navigation item. While all that seems straight forward, there are some issues in each of these widgets that you may want to cater to improve user experience.

Tabs — Usually you will use together DefaultTabController , TabBar and TabBarView to do the job. However, things become tricky if one of the “bodies” in TabBarView contains TextField/TextFormField. User taps on the TextField, device’s keyboard shows up, then user swipes tabs / taps on another tab, the keyboard won’t close automatically, blocking the user’s view when the keyboard is no longer needed. To fix this, you can listen to tab change by _controller.addListener(_yourCallback) where _controller is the one you declared as the widget’s variable but not the one in DefaultTabController. Insider _yourCallback, you can then create a dummy FocusNode’s and request focus on it in order to close the keyboard.

Keyboard-close-fix before (left) and after (right)

Bottom Navigation Bar — Flutter’s default theming for BottomNavigationBar is that background colour is consistently “white” for Brightness.light and “dark” for Brightness.dark (surface colour of ThemeData so to speak). For selected item, icon colour and label colour will be primary colour; for unselected item, they will be in light grey. However, if you want to follow some of the examples in Material Design Spec, you might want to set the background colour of each item to “primary”, label and icon colour to “onPrimary”, and apply opacity < 1 for unselected ones.

bottom navigation bar with (right) and without(left) custom fix

3. Form

Firstly, validation. Not just validate on form submit / on formState.validate( ), but validate when user is typing. Not just autoValidate = true neither. This option will invalidate a field even if our user hasn’t started input anything yet. So they are blamed wrong right from the start. We want to do autoValidate only after our users have made their first attempt (angular-ly speaking, touched).

Note: a TextFormField = FormField( child: TextField ).

A FormField can be integrated into a Form. When _formKey.validate() is called, all the FormFields inside Form will be validated by calling their own validators. So in most cases you may want to use TextFormField. Therefore below we will do customisation on a TextFormField. But if you prefer TextField in your project, no worries as the same technique can be applied.

Back to our auto-validate-issue, when we instantiate a TextFormField, we provide with a FocusNode instance, thereby adding listeners to the field’s “onFocus” / “onBlur” in html terms. We can have autoValidate = false at the beginning, then set it to true “on field blur”. So TextFormField will perform auto-validation only after user has made the first attempt filling the input (touched).

Next we will tackle date picker and time picker. Flutter provides convenient methods showDatePicker and showTimePicker calling which will give user a popup dialog with a calendar and a clock respectively. Easiest usage will be to call these methods in the onTap property of a RaiseButton/FlatButton/etc. However, for form-consistency sake you may want to have a TextFormField instead of buttons for triggering a date picker / time picker dialog. To do so there are a few things we need to customise:

focusNode: Allow us to add listener for “on field focus”. We then set _isPicking = true;

enabled: Disable the TextFormField on field focus so that device keyboard will not be triggered

on field focus so that device keyboard will not be triggered controller: Allow us to set value of TextFormField once user has picked a date, by _controller.text = DateFormat.yMMMd().format(picked);

date picker and time picker triggered by 2 TextFormFields

4. Card Layout

One way or another you may want to display some “items”, be it an album, an e-commerce product, social media feed, with a title, a subtitle, few lines of body text, and of course an image. Unlike other Flutter widgets such as AppBar, CheckBoxTile, SwitchTile, for which you can have neat layouts ready-made, Card widget only gives you “a rectangle with elevation”. You need to figure out your way to put things in it beautifully. Material Components Web’s catalogue page gives us 5 different layers to reference from. For example we can architect our Flutter layout like so:

Then we can create a re-usable widget so that we can import and use it everywhere in the app without putting up the layout again and again.

Re-use our card layout anywhere in the app

Summary

We’ve covered the following areas of concern when we start a Flutter project:

Theming — Pick primary and accent colours, setup ThemeData Changing Screens — choose 1 of 3: BottomNavigationBar, Drawer, Tab Form — auto-validation, date and time picker Card Layout — the 5 layouts from the Material Component Guide

More to be shared soon. Stay tuned.