Functional Themes
Accessing Functional Themes
Every AFib app starts with a default functional theme, called XXXDefaultTheme, and stored in
lib/ui/themes/xxx_default_theme.dart
. All themes are derived from AFFunctionalTheme
. That superclass
provides a lot of functionality. It's API docs are probably worth skimming through.
Every SPI has a theme member variable, also accessible via spi.t
or spi.theme
. Initially, all your UI components will
use your default theme.
The Purpose of Functional Themes
Functional themes are useful for accessing standardized UI attributes using typeahead, for sharing simple re-usable bits of UI generation across multiple screens, and for making third party UI libraries more flexible and extensible.
Accessing UI attributes with typeahead
When you first start using Flutter, it is very easy to end up with code like this:
Your default theme has many typeahead attributes which match the attributes commonly required by widgets. For example, you could use this instead:
final t = spi.theme;
return Container(
// the standard amount of padding, which you can easily configure globally.
padding: t.padding.standard,
// your biggest vertical amount of margin, again, configurable later.
margin: t.margin.v.biggest,
...
)
Note that if a widget takes a parameter like padding
, you can find what the theme has to offer
by typing t.pad
and looking for the typeahead.
For example, if I discovered that the BoxDecoration has color
and borderRadius
parameters, I could easily
discover that AFib provides some configurable defaults for those attributes:
return Container(
decoration: BoxDecoration(
// you'd fine this by typing t.col... and watching the typeahead, because
// the parameter itself is 'color:'
color: t.colorPrimary,
// just a border radius at the top.
borderRadius: t.borderRadius.t.standard,
)
);
decorationPrimaryHeader
method, which would capture the decoration of some conceptual UI element type, and use it like this, referring to t.colorPrimary
and t.borderRadius
from within it:
Or, I might discover styles the same way when configuring a Text
widget:
Adding Your Own Simple UI conventions
Although you can, in theory, make a widget class for every small piece of UI code you wish to standardize, I often find that tedious, and it does not play very well with typeahead.
For example, if I wish to use a single add icon across my entire app, I could place this in a separate file:
class XXXAddIcon extends Icon {
XXXAddIcon({
Color? color,
double? size,
}): super(Icons.add_box_sharp, color: color, size: size);
}
I would prefer to add a function to XXXDefaultTheme which achieves the same result:
Icon iconAdd({
Color? color,
double? size,
}) {
return Icon(Icons.add_box_sharp,
color: color,
size: size
);
}
Because this icon will be easily discoverable on the theme using the same principle I used
to find t.margin
or t.colorPrimary
.
final t = spi.theme;
return IconButton(
// as I type t.icon..., I will see typeahead for all the standardized icons in my app.
icon: t.iconAdd(),
...
);
Note that the vast majority of Flutter widgets take a child
parameter which is a widget, or a
children
parameter which is a list of widgets. Consequently, you will find utilities for creating
simple widgets, or lists of widgets, under t.child...
and t.children...
respectively.
Making Third Party UI Libraries More Flexible
As described in the section on reusing AFib-aware UI libraries below, you can override
the themes of third party libraries with your own code. Open-source UI libraries are encouraged to
add bespoke child...
methods to their themes, making it easy for you to override the functions and
change the appearance of their UIs in unexpected ways.
For example, in a simple case you might override a bespoke icon:
However, because everything is a widget in Flutter, you can also more significantly alter the UI. For example, if a third-party signin page has a sign-in button, and you wish to add a 'View Terms' text button below it, you might do something like:
@override
Widget childButtonSignin({
AFWidgetID? wid
required Widget child,
...
}) {
// note: we are inside the theme here, this is like t.column() in UI code.
final rows = column();
// add in the original button.
colExtra.add(super.childButtonPrimary(wid: wid, child: child));
// this is a simple example, but in-general, creating an AFib-aware child widget would work
// better, as it would have its own state view and SPI, and be easier to test.
colExtra.add(TextButton(
child: Text("View Terms"),
onPressed: () {
// note that the theme itself has a context member variable
context.navigatePush(TermsScreen.navigatePush());
)
);
return Column(children: colExtra);
}
Configuring Theme Attributes
Functional themes were originally called 'functional' because they are not allowed to have configurable data, they are only allowed to introduce functions. Each AFib application has a single AFFundamentalTheme
which contains configurable theme data. Functional themes can access this data, but they cannot have data of their own.
Configuring Your Fundamental Theme
The defineFundamentalTheme
function in your lib/initialization/xxx_define_core.dart
file configures your fundamental theme. If you look at that function, you will see that it is based on Flutter's native material themes, but also allows you to add custom data to your fundamental theme.
Populating Custom Theme Attributes
If you wish to populate your fundamental theme with custom data, you can use the AFAppFundamentalThemeAreaBuilder.setValue
in xxx_define_core.dart
:
void defineFundamentalTheme(AFFundamentalDeviceTheme device, AFComponentStates appState, AFAppFundamentalThemeAreaBuilder primary) {
primary.setValue(XXXThemeID.achievementColor, Colors.yellow);
}
You can then access it from within your functional theme using fundamentals.findValue
.
Creating Additional Themes
Note that the generate ui
subcommand supports creating additional custom themes. For example, you might use:
Accessing Third Party Themes
Because functional themes do not have their own data, AFib can always allow you to cast from on theme to another. For example, if you wanted to create buttons in your UI in the same style as the AFib Signin library's buttons, you could use: