How to create a better Flutter theme for each platform and brightness mode

Published on

Flutter provides you a way to customize your app theme.
It is extremely focused on material design but you can still customize it to fit your needs.

The problem

Flutter provides you a great theme to let you customize your app.
The problem is when you want to start getting out of material and customize your app theme.
Most designers will want to customize the theme to fit their needs and taste.

You create your theme directly without any factory. Meaning you will have many developers copy pasting colors throughout the app and the themeData file.

You can't switch the theme without restarting the app (dark/light?).
You don't have a way to set different theme for different platform.

The good news is that we can fix all of this.

Handling Dark and Light theme

Switching from light to dark mode should be easy.
The best way to do this is to not duplicate your theme but to create a factory that will create the theme from a color palette.

Flutter theme light and dark

Flutter and Material provide you a nice logical way to handle an app theme. Every color as an 'on' color.
Meaning that if you have a white background you should have a black text color.

Switching from light to dark should only rely on switching some colors.
You should not have to handle if case in your code.

Limit the number of colors

The more colors you have the more difficult it will be to maintain your app.
Simplicity is oftenly the best way to go.

Instead of having primary, secondary, accent, secondaryAccent, ...
Try making your app with one or two colors.
The more colors you incorporate, the more challenging it becomes to maintain your app. Simplicity is often the best approach. Instead of using primary, secondary, accent, secondaryAccent, and so forth, consider designing your app with just one or two colors.

(Please note that black, white, and greys are exceptions and are not considered as colors.)

Flutter color palette

For example you can check some of the most popular apps.
Facebook, Instagram, Twitter, Youtube, ...
They don't use a lot of colors.

Creating a theme factory

As we want to create our theme for maybe different platforms or different color palette we will create a factory.

abstract class ApparenceKitThemeDataFactory {
  const ApparenceKitThemeDataFactory();

  ApparenceKitThemeData build({
    required ApparenceKitColors colors,
    required ApparenceKitTextTheme defaultTextStyle,
  });
}

This factory will take a color palette and a default text theme and will create a theme.
The default text theme contains the default text style and fonts for all our text. As the colors I also recommend to limit the number of fonts you use.
Our designer at Apparence tend to limit his usage to 2 fonts.
(There is some website that give you inspirations for 2 fonts that go well together...)

Using our factory

Now that we have our factory we can use it to create our theme.

class UniversalThemeFactory extends ApparenceKitThemeDataFactory {
  const UniversalThemeFactory();

  
  ApparenceKitThemeData build({
    required ApparenceKitColors colors,
    required ApparenceKitTextTheme defaultTextStyle,
  }) {
    return ApparenceKitThemeData(
      colors: colors,
      defaultTextTheme: defaultTextStyle,
      materialTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: colors.primary).copyWith(
          surface: colors.surface,
          onSurface: colors.onSurface,
          background: colors.background,
          onBackground: colors.onBackground,
          primary: colors.primary,
          error: colors.error,
        ),
        elevatedButtonTheme: elevatedButtonTheme(
          colors: colors,
          textTheme: defaultTextStyle,
        ),
        inputDecorationTheme: inputDecorationTheme(
          colors: colors,
          textTheme: defaultTextStyle,
        ),
        textTheme: textTheme(
          colors: colors,
          defaultTextStyle: defaultTextStyle,
        ),
      ),
      ...
    );
  }

As you can see we are using the color palette and the default text theme to create our theme.
Now changing colors or text style won't be done here.

We have defined our own color palette and default text files separetly.

Providing our theme through the app

As we want to access our theme from anywhere in our app we will use an InheritedNotifier.
This will allow us to access our theme from the BuildContext.
But also to switch the mode of our theme without restarting the app.

/// We use this to access the theme from the BuildContext in all our widgets
/// We don't use riverpod here so we can get the theme from the context and regular widgets
class ThemeProvider extends InheritedNotifier<AppTheme> {
  const ThemeProvider({
    super.key,
    super.notifier,
    required super.child,
  });

  
  bool updateShouldNotify(covariant InheritedNotifier<AppTheme> oldWidget) {
    ...
  }

  static AppTheme of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<ThemeProvider>()!.notifier!;
}

Using our theme ThemeProvider

Now that we have our ThemeProvider we can use it in our app.
It will be above our MaterialApp.

class MyApp extends StatelessWidget {
  const MyApp({
    super.key,
  });

  // This widget is the root of your application.
  
  Widget build(BuildContext context, WidgetRef ref) {
    return ThemeProvider(
      notifier: AppTheme.uniform(
        themeFactory: const UniversalThemeFactory(),
        lightColors: ApparenceKitColors.light(),
        darkColors: ApparenceKitColors.dark(),
        textTheme: ApparenceKitTextTheme.build(),
        defaultMode: ThemeMode.light,
      ),
      child: Builder(builder: (context) {
        return MaterialApp(
          title: 'Flutter Pro Starter Kit',
          initialRoute: 'home',
          theme: ThemeProvider.of(context).light,
          darkTheme: ThemeProvider.of(context).dark,
          themeMode: ThemeProvider.of(context).mode,

If you don't want to use dark mode you can just remove it.

This way we can access our theme from anywhere in our app.
Using for example

ThemeProvider.of(context).current

or still using the regular Theme.of(context)

Theme.of(context).textTheme.headline1

Creating theme shortcuts

As we want to access our theme from anywhere in our app we will create some shortcuts.
This will allow us to access our theme from the BuildContext.
But also to switch the mode of our theme without restarting the app.

extension ThemeExtension on BuildContext {
  // access the theme from anywhere in the app using context.theme  
  ApparenceKitThemeData get theme => ThemeProvider.of(this).current;
  // access the color palette from anywhere in the app using context.colors  
  ApparenceKitColors get colors => ThemeProvider.of(this).current.colors;
  // access the text theme from anywhere in the app using context.textTheme
  ApparenceKitTextTheme get textTheme => ThemeProvider.of(this).current.textTheme;
  // access the theme mode from anywhere in the app using context.themeMode
  ThemeMode get themeMode => ThemeProvider.of(this).mode;
  // toggle the theme mode from anywhere in the app using context.toggleTheme
  void toggleTheme() => ThemeProvider.of(this).toggle();
}

Conclusion

There is tons of things to say about handling theme from design to code.
multiplatform theme, gradients, shadows, ...

I hope this article will help you to get started with creating a great multiplatform theme.
This way of handling theme is the one we use at Apparence and we included it in our starter kit.

Create a 5 stars app using our Flutter templates

Check our flutter boilerplate
kickstarter for flutter apps
Read more
You may also be interested in
Flutter flex demystified  blog card image
Flutter flex demystified
Published on 2023-10-16
Our Flutter Template is Here!  blog card image
Our Flutter Template is Here!
Published on 2023-10-10
FlutterKit by Apparence.io © 2023. All rights reserved