InheritedWidget: Custom Theme Class

There are a few global style options that are glaringly missing fromFlutter's built in theme. For example, consistent spacing might be _the most_important part of good layout design.

If you're working with a team, maybe you want to ensure that everyone workingon your app uses the exactsame spacing guidelines. You can achieve this by creating your ownInheritedWidget that passes props across the whole app. Theme is aninherited Widget, so it would work exactly like that.

Set Up a Custom Theme

The InheritedWidget takes a bit of boiler plate to set up, so if it's yourfirst time, you may benefit from a 'zero-to-one' explanation:

Here's a detailed explanation of how to use an InhertiedWidget.

What you'll actually need to set this up is 3 classes:

  1. class LayoutThemeContainer extends StatefulWidget
  2. class LayoutThemeState extends State<LayoutThemeContainer>
  3. class _InheritedStateContainer extends InheritedWidget

LayoutThemeContainer is just a standard StatefulWidget, with on bonus method: of.

  1. static LayoutThemeState of(BuildContext context) {
  2. return (context.inheritFromWidgetOfExactType(_InheritedStateContainer)
  3. as _InheritedStateContainer)
  4. .data;
  5. }

Your state class is pretty simple also:

  1. class LayoutThemeState extends State<LayoutThemeContainer> {
  2. // Add all your theme properties and logic here:
  3. double get spacingUnit => 10.0;
  4. @override
  5. Widget build(BuildContext context) {
  6. return new _InheritedStateContainer(
  7. data: this,
  8. child: widget.child,
  9. );
  10. }
  11. }

And finally, a standard InheritedWidget class:

  1. class _InheritedStateContainer extends InheritedWidget {
  2. final LayoutThemeState data;
  3. _InheritedStateContainer({
  4. Key key,
  5. @required this.data,
  6. @required Widget child,
  7. }) : super(key: key, child: child);
  8. @override
  9. bool updateShouldNotify(_InheritedStateContainer old) => true;
  10. }

Use the Custom Theme

1. Wrap your app

First, you need to wrap your app in the theme, so you can access it everywhere:

  1. class MyApp extends StatelessWidget {
  2. // This widget is the root of your application.
  3. @override
  4. Widget build(BuildContext context) {
  5. return LayoutThemeContainer(
  6. child: new MaterialApp(
  7. title: 'Flutter Demo',
  8. theme: Theme.of(context).copyWith(
  9. primaryColor: Colors.amber,
  10. textTheme: Typography(platform: TargetPlatform.iOS).black),
  11. home: new MyHomePage(title: 'Flutter Demo Home Page'),
  12. ),
  13. );
  14. }
  15. }

2. Use the props:

  1. new Container(
  2. padding: new EdgeInsets.all(
  3. // Just like you'd use Theme.of or MediaQuery.of
  4. LayoutThemeContainer.of(context).spacingUnit,
  5. ),
  6. decoration: new BoxDecoration(
  7. color: Colors.cyanAccent,
  8. ),
  9. child: new Text('You have pushed the button this many times:'),
  10. ),

Using an inherited widget

3. A design system suggestion:

At the company I work for, we mostly follow material guidelines, but wehave some of our own design conventions in place. In order to ensure thateveryone is on the same page, we use a custom theme that forces us to workwithin our own rules.

For example, if you wanted everyone to use an 8pt grid (a la Material design):

  1. class LayoutThemeState extends State<LayoutThemeContainer> {
  2. // Generic Material Spacing Sizes
  3. static const double _matGridUnit = 8.0;
  4. static double matGridUnit({scale = 1}) {
  5. // Material design grid uses multiples of 8
  6. // Multiples of 4 are acceptable if needed
  7. // Only accept numbers that are full or half units of _matGridUnit
  8. assert(scale % .5 == 0);
  9. return _matGridUnit * scale;
  10. }
  11. ...

Then to use it:

  1. new Container(
  2. padding: new EdgeInsets.all(
  3. LayoutThemeContainer.of(context).matGridUnit(scale: 2.5),
  4. ),

It's that easy, and super useful as your team and app grows.