Use the powerful theme framework in GetX

1. Introduction

FlexColorScheme is a very powerful theme framework for Flutter, there is complete documentation and tutorials, and there are many existing themes that can be used, you also can design your theme online.

FlexColorScheme is at its core an advanced ThemeData factory. It makes customization of ThemeData simpler. It does not expose everything you can do with ThemeData via its own APIs. Its focus is on things that are complex and tedious to do with ThemeData. You can further tune the produced ThemeData by using copyWith and ThemeData’s own APIs.

The Themes Playground is a visual web configuration tool for the FlexColorScheme API. It allows you to play with different styles interactively, and copy the FlexColorScheme API setup code for the theme you created. The Playground does not cover all APIs offered by FlexColorScheme, but most of them, certainly the most commonly used ones.

Do you want to be a good trading in cTrader?   >> TRY IT! <<

2. Use in GetX

Because GetX can easy to help with dynamic change and handle the theme, I will base it on my previous article “Create Flutter project with GetX Pattern” to show you how to use FlexColorScheme in GetX.

2.1 Get the themes from FlexColorScheme

Create the theme folder under /lib/app/ui/, go to FlexColorScheme‘s playground to design or choose the existing theme, and click the Copy theme code button, then you can easy to copy the theme codes.

the theme code should be like below:

// Theme config for FlexColorScheme version 7.3.x. Make sure you use
// same or higher package version, but still same major version. If you
// use a lower package version, some properties may not be supported.
// In that case remove them after copying this theme to your app.
theme: FlexThemeData.light(
  scheme: FlexScheme.orangeM3,
  surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
  blendLevel: 7,
  subThemesData: const FlexSubThemesData(
    blendOnLevel: 10,
    blendOnColors: false,
    useTextTheme: true,
    useM2StyleDividerInM3: true,
    adaptiveRadius: FlexAdaptive.excludeWebAndroidFuchsia(),
    alignedDropdown: true,
    useInputDecoratorThemeInDialogs: true,
  ),
  visualDensity: FlexColorScheme.comfortablePlatformDensity,
  useMaterial3: true,
  swapLegacyOnMaterial3: true,
  // To use the Playground font, add GoogleFonts package and uncomment
  // fontFamily: GoogleFonts.notoSans().fontFamily,
),
darkTheme: FlexThemeData.dark(
  scheme: FlexScheme.orangeM3,
  surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
  blendLevel: 13,
  subThemesData: const FlexSubThemesData(
    blendOnLevel: 20,
    useTextTheme: true,
    useM2StyleDividerInM3: true,
    adaptiveRadius: FlexAdaptive.excludeWebAndroidFuchsia(),
    alignedDropdown: true,
    useInputDecoratorThemeInDialogs: true,
  ),
  visualDensity: FlexColorScheme.comfortablePlatformDensity,
  useMaterial3: true,
  swapLegacyOnMaterial3: true,
  // To use the Playground font, add GoogleFonts package and uncomment
  // fontFamily: GoogleFonts.notoSans().fontFamily,
),
// If you do not have a themeMode switch, uncomment this line
// to let the device system mode control the theme mode:
// themeMode: ThemeMode.system,

but we still need to change something, we need to create a class to handle each theme, for the above theme code, we can create the class ThemeBrown below

create a file /lib/app/ui/theme/theme_brown.dart and past the theme code:

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

class ThemeBrown {
  static ThemeData lightTheme() => FlexThemeData.light(
      scheme: FlexScheme.orangeM3,
      surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
      blendLevel: 7,
      subThemesData: const FlexSubThemesData(
        blendOnLevel: 10,
        blendOnColors: false,
        useTextTheme: true,
        useM2StyleDividerInM3: true,
        adaptiveRadius: FlexAdaptive.excludeWebAndroidFuchsia(),
        alignedDropdown: true,
        useInputDecoratorThemeInDialogs: true,
      ),
      visualDensity: FlexColorScheme.comfortablePlatformDensity,
      useMaterial3: true,
      swapLegacyOnMaterial3: true,
      // To use the Playground font, add GoogleFonts package and uncomment
      // fontFamily: GoogleFonts.notoSans().fontFamily,
    );

  static ThemeData darkTheme() => FlexThemeData.dark(
      scheme: FlexScheme.orangeM3,
      surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold,
      blendLevel: 13,
      subThemesData: const FlexSubThemesData(
        blendOnLevel: 20,
        useTextTheme: true,
        useM2StyleDividerInM3: true,
        adaptiveRadius: FlexAdaptive.excludeWebAndroidFuchsia(),
        alignedDropdown: true,
        useInputDecoratorThemeInDialogs: true,
      ),
      visualDensity: FlexColorScheme.comfortablePlatformDensity,
      useMaterial3: true,
      swapLegacyOnMaterial3: true,
      // To use the Playground font, add GoogleFonts package and uncomment
      // fontFamily: GoogleFonts.notoSans().fontFamily,
    );
}

as you can see, we put the light and dark theme to lightTheme and darkTheme method in ThemeBrown class, and then we can easy to use them later.

for example, I want to create 10 themes in my App, just repeat the above flow and create 10 theme classes and files

2.2 Create theme service

Because we need to handle multiple themes, we can create a service for handling the below theme logic:

  1. define the theme’s name
  2. get the theme by name
  3. get the current theme mode from storage

so the file should be /lib/app/data/service/theme_service.dart:

import 'package:get_storage/get_storage.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '../../ui/theme/theme_brown.dart';
import '../../ui/theme/theme_deepBlue.dart';
import '../../ui/theme/theme_gold.dart';
import '../../ui/theme/theme_green.dart';
import '../../ui/theme/theme_hippie_blue.dart';
import '../../ui/theme/theme_indigo.dart';
import '../../ui/theme/theme_orang.dart';
import '../../ui/theme/theme_purple.dart';
import '../../ui/theme/theme_red.dart';
import '../../ui/theme/theme_sakura.dart';

class ThemeService {
  //define each theme's name
  static const String Red = 'Red';
  static const String Indigo = 'Indigo';
  static const String HippieBlue = 'HippieBlue';
  static const String Green = 'Green';
  static const String DeepBlue = 'DeepBlue';
  static const String Sakura = 'Sakura';
  static const String Purple = 'Purple';
  static const String Brown = 'Brown';
  static const String Gold = 'Gold';
  static const String Orang = 'Orang';

  //use GetStorage to handle the current theme mode
  final _getStorage = GetStorage();
  final _storageKey = 'ThemeMode';
  static ThemeService instance = ThemeService._();
  // ignore: empty_constructor_bodies
  ThemeService._() {}
  set themeMode(ThemeMode themeMode) {
    if (themeMode == ThemeMode.system) {
      _getStorage.remove(_storageKey);
    } else {
      _getStorage.write(_storageKey, themeMode == ThemeMode.dark);
    }
    Get.changeThemeMode(themeMode);
  }

  ThemeMode get themeMode {
    switch (_getStorage.read(_storageKey)) {
      case true:
        return ThemeMode.dark;
      case false:
        return ThemeMode.light;
      default:
        return ThemeMode.system;
    }
  }

  //Get the theme by name and handle light and dark mode
  ThemeData getTheme(String name, {bool isDark = false}) {
    ThemeData currTheme = ThemeData();
    switch (name) {
      case Red:
        currTheme = isDark ? ThemeRed.darkTheme() : ThemeRed.lightTheme();
        break;
      case Indigo:
        currTheme = isDark ? ThemeIndigo.darkTheme() : ThemeIndigo.lightTheme();
        break;
      case HippieBlue:
        currTheme =
            isDark ? ThemeHippieBlue.darkTheme() : ThemeHippieBlue.lightTheme();
        break;
      case Green:
        currTheme = isDark ? ThemeGreen.darkTheme() : ThemeGreen.lightTheme();
        break;
      case DeepBlue:
        currTheme =
            isDark ? ThemeDeepBlue.darkTheme() : ThemeDeepBlue.lightTheme();
        break;
      case Sakura:
        currTheme = isDark ? ThemeSakura.darkTheme() : ThemeSakura.lightTheme();
        break;
      case Purple:
        currTheme = isDark ? ThemePurple.darkTheme() : ThemePurple.lightTheme();
        break;
      case Brown:
        currTheme = isDark ? ThemeBrown.darkTheme() : ThemeBrown.lightTheme();
        break;
      case Gold:
        currTheme = isDark ? ThemeGold.darkTheme() : ThemeGold.lightTheme();
        break;
      case Orang:
        currTheme = isDark ? ThemeOrang.darkTheme() : ThemeOrang.lightTheme();
        break;
    }

    return currTheme;
  }
}

2.3 Show the theme selection

We need to show the theme selection to the user to choose which theme they want to use, to simplify the operation, I use babstrap_settings_screen and hardcode to put the theme items in a ListView, of course you can use other way to present it.

below is only a code snippet for presenting one theme item with babstrap_settings_screen in ListView in UI page

/lib/app/ui/pages/settings_theme_page/settings_theme_page.dart

ListView(
  children: [
    SettingsGroup(
      items: [
        SettingsItem(
          onTap: () {
            controller.changeTheme(
                isDarkMode,
                ThemeService.Red,
                isDarkMode
                    ? ThemeRed.darkTheme()
                    : ThemeRed.lightTheme());
          },
          icons: CupertinoIcons.paintbrush_fill,
          iconStyle: IconStyle(
            iconsColor: Colors.white,
            withBackground: true,
            backgroundColor: Colors.red,
          ),
          title: LabelKeys.theme1.tr,
          trailing: Radio<String>(
            value: ThemeItem.Red,
            groupValue: controller.selectedValue.value,
            activeColor: Theme.of(context)
                .primaryColor, // Change the active radio button color here
            fillColor: MaterialStateProperty.all(Theme.of(context)
                .secondaryHeaderColor), // olor when selected
            splashRadius:
                20, // Change the splash radius when clicked
            onChanged: (value) {
              controller.changeTheme(
                  isDarkMode,
                  value!,
                  isDarkMode
                      ? ThemeRed.darkTheme()
                      : ThemeRed.lightTheme());
            },
          ),
        ),
      ],
    ),
  ],
)

and we can get the current theme mode by the below code

Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
bool isDarkMode = currentBrightness == Brightness.dark;

and handle the backend logic in controller

/lib/app/controllers/settings_theme_controller.dart

class SettingsThemeController extends GetxController {
  //define a variable to save selected theme value
  var selectedValue = Global.defaultTheme.obs;
  //use the local storage to save the selected theme
  var localStorage = Get.find<LocalStorageService>();

  @override
  void onReady() async {
    super.onReady();

    //get the current theme from local storage
    var currTheme =
        await localStorage.getValue<String>(LocalStorageKeys.currentTheme);

    //set the current theme
    selectedValue.value = currTheme!;
  }

  //change the theme when user click the item
  void changeTheme(bool isDarkMode, String name, ThemeData theme) {
    selectedValue.value = name;

    localStorage.setValue<String>(LocalStorageKeys.currentTheme, name);
    Get.changeThemeMode(isDarkMode ? ThemeMode.dark : ThemeMode.light);
    Get.changeTheme(theme);
  }
}

2.4 Handle default theme when App startup

In the end, we need to handle the default theme or selected theme when the app starts. So we need to update the main.dart file

If you create the GetX project like this article, you should get something like the below code in the main.dart

void main() async {

    WidgetsFlutterBinding.ensureInitialized();
    DependecyInjection.init();

    runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {

    return ScreenUtilInit(
      builder: (_, __) {
        return GetMaterialApp(
          title: 'Win_reading_harry_potter',
          debugShowCheckedModeBanner: false,
          translations: Translation(),
          initialBinding: AppBindings(),
          initialRoute: AppRoutes.SPLASH,
          unknownRoute: AppPages.unknownRoutePage,
          getPages: AppPages.pages,
          builder: (_, child) {
            return MainLayout(child: child!);
          },
        );
      },
      //! Must change it to true if you want to use the ScreenUtil
      designSize: const Size(411, 823),
    );
  }
}

we need to add some codes to support the theme

void main() async {

  ...

  //get the selected theme from local storage
  var localStorage = LocalStorageService();
  var currTheme =
      await localStorage.getValue<String>(LocalStorageKeys.currentTheme);

 //if there is no theme for first time, then use the default theme
 if (currTheme == null) {
    currTheme = Global.defaultTheme;
    localStorage.setValue<String>(
        LocalStorageKeys.currentTheme, Global.defaultTheme);
  }

  //pass the current theme to material app
  runApp(MyApp(currTheme: currTheme));
}

so also update the below

class MyApp extends StatelessWidget {
  //support the parameter for setting the current theme
  const MyApp({super.key, this.currTheme});

  final String? currTheme;

 @override
  Widget build(BuildContext context) {
    //get the light and dark theme base on current theme
    ThemeData lightTheme = currTheme == null
        ? ThemeDeepBlue.lightTheme()
        : ThemeItem.getTheme(currTheme!);
    ThemeData darkTheme = currTheme == null
        ? ThemeDeepBlue.darkTheme()
        : ThemeItem.getTheme(currTheme!, isDark: true);

    return ScreenUtilInit(
      builder: (_, __) {
        return GetMaterialApp(
          title: 'Win_reading_harry_potter',
          debugShowCheckedModeBanner: false,
          theme: lightTheme,    //set the light theme
          darkTheme: darkTheme, //set the dark theme
          themeMode: ThemeService.instance.themeMode, //set the default theme mode
          translations: Translation(),
          initialBinding: AppBindings(),
          initialRoute: AppRoutes.SPLASH,
          unknownRoute: AppPages.unknownRoutePage,
          getPages: AppPages.pages,
          builder: (_, child) {
            return MainLayout(child: child!);
          },
        );
      },
      //! Must change it to true if you want to use the ScreenUtil
      designSize: const Size(411, 823),
    );
  }
}

Done!

3. Conclusion

FlexColorScheme can help to make beautiful Flutter Material Design themes, and GetX and easy to handle and change the theme dynamic, so if you combine them, you will find that a great job!

Loading

Views: 4
Total Views: 117 ,

Leave a Reply

Your email address will not be published. Required fields are marked *