Material 3 Flutter : A Dark Theme in Web App

We’ve been building a Firebase, Firestore and Provider based web app where we have already used Material design 3.

Firstly, what is Material design 3? It’s the next generation design that will rule the cross platform application world.

Secondly, in our previous section we have changed the look of the existing web app. However, we used the light mode.

Finally, we want to change it to dark mode and see how it looks.

Besides, we want to show the code and the implementation.

However, it’s always good to look back and recapitulate.

Therefore, we must remember why we use Flutter?

We use Flutter for building beautiful applications for mobile, web, desktop, and embedded devices from a single codebase. Right? 

In addition, Flutter always implements the Material Design guidelines. 

Moreover, there are lots of Material Widgets that implement these guidelines.

Let’s take a quick look at the list of widgets that can implement Material 3.

You can get more widgets in the widget catalog.

Let’s take a look at the previous design which used the light mode.

Flutter 3.0 Material 3 Theme
Flutter 3.0 Material 3 Theme

But the design changes when we use dark mode. It no longer remains the same.

Flutter Material 3 Web App
Flutter Material 3 Web App

Firstly, we have used a Theme Provider to customize our theme.

Secondly, let’s watch the code.

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:material_color_utilities/material_color_utilities.dart';

class ThemeSettingChange extends Notification {
  ThemeSettingChange({required this.settings});
  final ThemeSettings settings;
}

class ThemeProvider extends InheritedWidget {
  const ThemeProvider(
      {super.key,
      required this.settings,
      required this.lightDynamic,
      required this.darkDynamic,
      required super.child});

  final ValueNotifier<ThemeSettings> settings;
  final ColorScheme? lightDynamic;
  final ColorScheme? darkDynamic;

  Color custom(CustomColor custom) {
    if (custom.blend) {
      return blend(custom.color);
    } else {
      return custom.color;
    }
  }

  Color blend(Color targetColor) {
    return Color(
        Blend.harmonize(targetColor.value, settings.value.sourceColor.value));
  }

  Color source(Color? target) {
    Color source = settings.value.sourceColor;
    if (target != null) {
      source = blend(target);
    }
    return source;
  }

  ColorScheme colors(Brightness brightness, Color? targetColor) {
    final dynamicPrimary = brightness == Brightness.light
        ? lightDynamic?.primary
        : darkDynamic?.primary;
    return ColorScheme.fromSeed(
      seedColor: dynamicPrimary ?? source(targetColor),
      brightness: brightness,
    );
  }

  ShapeBorder get shapeMedium => RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(8),
      );

  CardTheme cardTheme() {
    return CardTheme(
      elevation: 0,
      shape: shapeMedium,
      clipBehavior: Clip.antiAlias,
    );
  }

  ListTileThemeData listTileTheme(ColorScheme colors) {
    return ListTileThemeData(
      shape: shapeMedium,
      selectedColor: colors.secondary,
    );
  }

  AppBarTheme appBarTheme(ColorScheme colors) {
    return AppBarTheme(
      elevation: 0,
      backgroundColor: colors.surface,
      foregroundColor: colors.onSurface,
    );
  }

  TabBarTheme tabBarTheme(ColorScheme colors) {
    return TabBarTheme(
      labelColor: colors.secondary,
      unselectedLabelColor: colors.onSurfaceVariant,
      indicator: BoxDecoration(
        border: Border(
          bottom: BorderSide(
            color: colors.secondary,
            width: 2,
          ),
        ),
      ),
    );
  }

  BottomAppBarTheme bottomAppBarTheme(ColorScheme colors) {
    return BottomAppBarTheme(
      color: colors.surface,
      elevation: 0,
    );
  }

  BottomNavigationBarThemeData bottomNavigationBarTheme(ColorScheme colors) {
    return BottomNavigationBarThemeData(
      type: BottomNavigationBarType.fixed,
      backgroundColor: colors.surfaceVariant,
      selectedItemColor: colors.onSurface,
      unselectedItemColor: colors.onSurfaceVariant,
      elevation: 0,
      landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
    );
  }

  NavigationRailThemeData navigationRailTheme(ColorScheme colors) {
    return const NavigationRailThemeData();
  }

  DrawerThemeData drawerTheme(ColorScheme colors) {
    return DrawerThemeData(
      backgroundColor: colors.surface,
    );
  }

  ThemeData light([Color? targetColor]) {
    final colorScheme = colors(Brightness.light, targetColor);
    return ThemeData.light().copyWith(
      //pageTransitionsTheme: pageTransitionsTheme,
      colorScheme: colorScheme,
      appBarTheme: appBarTheme(colorScheme),
      cardTheme: cardTheme(),
      listTileTheme: listTileTheme(colorScheme),
      bottomAppBarTheme: bottomAppBarTheme(colorScheme),
      bottomNavigationBarTheme: bottomNavigationBarTheme(colorScheme),
      navigationRailTheme: navigationRailTheme(colorScheme),
      tabBarTheme: tabBarTheme(colorScheme),
      drawerTheme: drawerTheme(colorScheme),
      scaffoldBackgroundColor: colorScheme.background,
      useMaterial3: true,
    );
  }

  ThemeData dark([Color? targetColor]) {
    final colorScheme = colors(Brightness.dark, targetColor);
    return ThemeData.dark().copyWith(
      colorScheme: colorScheme,
      appBarTheme: appBarTheme(colorScheme),
      cardTheme: cardTheme(),
      listTileTheme: listTileTheme(colorScheme),
      bottomAppBarTheme: bottomAppBarTheme(colorScheme),
      bottomNavigationBarTheme: bottomNavigationBarTheme(colorScheme),
      navigationRailTheme: navigationRailTheme(colorScheme),
      tabBarTheme: tabBarTheme(colorScheme),
      drawerTheme: drawerTheme(colorScheme),
      scaffoldBackgroundColor: colorScheme.background,
      useMaterial3: true,
    );
  }

  ThemeMode themeMode() {
    return settings.value.themeMode;
  }

  ThemeData theme(BuildContext context, [Color? targetColor]) {
    final brightness = MediaQuery.of(context).platformBrightness;
    return brightness == Brightness.light
        ? light(targetColor)
        : dark(targetColor);
  }

  static ThemeProvider of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ThemeProvider>()!;
  }

  @override
  bool updateShouldNotify(covariant ThemeProvider oldWidget) {
    return oldWidget.settings != settings;
  }
}

class ThemeSettings {
  ThemeSettings({
    required this.sourceColor,
    required this.themeMode,
  });

  final Color sourceColor;
  final ThemeMode themeMode;
}

Color randomColor() {
  return Color(Random().nextInt(0xFFFFFFFF));
}

// Custom Colors
const linkColor = CustomColor(
  name: 'Link Color',
  color: Color(0xFF00B0FF),
);

class CustomColor {
  const CustomColor({
    required this.name,
    required this.color,
    this.blend = true,
  });

  final String name;
  final Color color;
  final bool blend;

  Color value(ThemeProvider provider) {
    return provider.custom(this);
  }
}

By the way, we have highlighted the dark ThemeData section so that you can follow along with the code.

But, how will we use the custom Material 3 and implement the design to our web app?

Material 3 Flutter : Parent and Child

Probably you may have noticed how we have extended the Theme Provider class. 

Because it extends an Inherited Widget.

class ThemeProvider extends InheritedWidget {
  const ThemeProvider(
      {super.key,
      required this.settings,
      required this.lightDynamic,
      required this.darkDynamic,
      required super.child});

  final ValueNotifier<ThemeSettings> settings;
...

As a result, any child theme can use the theme. To do that we will create an instance at the very beginning and pass it to the scoped theme object in MaterialApp.

Now any nested child widget can inherit the theme. Right? 

import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'model/state_of_application.dart';

import 'model/theme.dart';
import 'view/chat_app.dart';

/// moving to second branch
final settings = ValueNotifier(ThemeSettings(
  sourceColor: Colors.pink,
  themeMode: ThemeMode.system,
));
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => StateOfApplication(),
      builder: (context, _) => DynamicColorBuilder(
        builder: (lightDynamic, darkDynamic) => ThemeProvider(
          lightDynamic: lightDynamic,
          darkDynamic: darkDynamic,
          settings: settings,
          child: ChatApp(),
        ),
      ),
    ),
  );
}
// 

By the way, to make this happen, we have used a package – dynamic color.

After that we have added the dependency to our “pubspec.yaml” file.

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  firebase_core: ^1.14.0
  firebase_auth: ^3.3.13
  cloud_firestore: ^3.1.11
  google_fonts: ^2.3.1
  provider: ^6.0.2
  material_color_utilities: ^0.1.4  
  dynamic_color: ^1.1.2

Now we can pass the ThemeData to the MaterialApp widget and set the theme mode to dark.

import 'package:flutter/material.dart';
//import 'package:dynamic_color/dynamic_color.dart';

//import 'package:google_fonts/google_fonts.dart';

import '../main.dart';
import 'chat_home_page.dart';
import '../model/theme.dart';

class ChatApp extends StatelessWidget {
  ChatApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final theme = ThemeProvider.of(context);
    return MaterialApp(
      title: 'Provider Firebase Blog',
      debugShowCheckedModeBanner: false,
      theme: theme.dark(settings.value.sourceColor),
      home: const ChatHomePage(),
    );
  }
}

You may have noticed that we have used Pink as the source color. 

Certainly you can change it in your main file, where we have defined the source color.

final settings = ValueNotifier(ThemeSettings(
  sourceColor: Colors.pink,
  themeMode: ThemeMode.system,
));
void main() {
  runApp(
    ChangeNotifierProvider(
....

Of course we will discuss and learn more about using the Material 3 Flutter design.For this step you may clone the GitHub repository.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

Courses at Educative

GitHub repository

Python and Data Science

Flutter, Dart and Algorithm

Twitter

Comments

One response to “Material 3 Flutter : A Dark Theme in Web App”

  1. […] Therefore, in this section we will learn to write a Flutter app that sets its theme once and looks beautiful across all platforms. […]

Leave a Reply