1

I am trying to write a custom ThemeData which I can pass to the extensions property of the ThemeData and be able to access them easily in my project. I have attached below the minimal code of what I am trying to achieve. As in the example code below, I want to be able to apply the theme simply by assigning to the style property as style: MyCustomAppTheme.of(context).specialTextStyle . The code below contains all the files of my minimal project: main.dart, theme.dart and my_custom_themedata.dart .

The problem: So, far I have not been able to override the lerp() (linear interpolation) method properly. Also there is a problem with my extending of the ThemeData class.

Codes:

main.dart:

import 'package:extending_flutter_themedata/themes/theme.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: MyCustomAppTheme.lightTheme,
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).primaryColor,
        title: const Text("Extending ThemeData"),
      ),
      body: Text(
        "Hello world",
        style: MyCustomAppTheme.of(context).specialTextStyle,
      ),
    );
  }
}

theme.dart:

import 'package:extending_flutter_themedata/themes/extending_themedata/my_custom_themedata.dart';
import 'package:flutter/material.dart';

class MyCustomAppTheme {
  MyCustomAppTheme._();

  static ThemeData lightTheme = ThemeData(
    useMaterial3: true,
    fontFamily: "Roboto",
    brightness: Brightness.light,
    extensions: <ThemeExtension<dynamic>>[
      MyCustomThemeData(
        customBackgroundColor: Colors.pink,
        specialTextStyle: const TextStyle(
          fontSize: 20.0,
          fontWeight: FontWeight.bold,
        ),
      ),
    ],
  );
}

my_custom_themedata.dart:

import 'package:flutter/material.dart';

class MyCustomThemeData extends ThemeExtension<MyCustomThemeData> {
  final Color customBackgroundColor;
  final TextStyle specialTextStyle;

  MyCustomThemeData({
    this.customBackgroundColor = Colors.blue,
    this.specialTextStyle = const TextStyle(),
  });

  @override
  MyCustomThemeData copyWith({
    Color? customBackgroundColor,
    TextStyle? specialTextStyle,
  }) {
    return MyCustomThemeData(
      customBackgroundColor:
          customBackgroundColor ?? this.customBackgroundColor,
      specialTextStyle: specialTextStyle ?? this.specialTextStyle,
    );
  }

  static MyCustomThemeData of(BuildContext context) {
    return Theme.of(context).extension<MyCustomThemeData>()!;
  }

  @override
  MyCustomThemeData lerp(MyCustomThemeData? a, MyCustomThemeData? b, double t) {
    if (a == null) return b!;
    if (b == null) return a;

    return MyCustomThemeData(
      customBackgroundColor:
          Color.lerp(a.customBackgroundColor, b.customBackgroundColor, t)!,
      specialTextStyle:
          TextStyle.lerp(a.specialTextStyle, b.specialTextStyle, t)!,
    );
  }

  static const id = 'my_custom_theme_data';
}

extension ThemeDataExtension on ThemeData {
  MyCustomThemeData? get myCustomThemeData => extension<MyCustomThemeData>();

  ThemeData copyWithCustom({MyCustomThemeData? myCustomThemeData}) {
    return copyWith(
      extensions: <ThemeExtension<dynamic>>{
        if (myCustomThemeData != null) myCustomThemeData,
      },
    );
  }

  static ThemeData lerp(ThemeData? a, ThemeData? b, double t) {
    return ThemeData.lerp(a, b, t)?.copyWithCustom(
      myCustomThemeData: MyCustomThemeData.lerp(
        a?.extension<MyCustomThemeData>(),
        b?.extension<MyCustomThemeData>(),
        t,
      ),
    );
  }
}

Update 01:

I have changes the lerp override to as follows and now some problems have resolved.

@override
MyCustomThemeData lerp(ThemeExtension<MyCustomThemeData>? other, double t) {
  if (other is! MyCustomThemeData) {
    return this;
  }

  return MyCustomThemeData(
    customBackgroundColor:
        Color.lerp(customBackgroundColor, other.customBackgroundColor, t)!,
    specialTextStyle:
        TextStyle.lerp(specialTextStyle, other.specialTextStyle, t)!,
  );
}

Now, I am able to apply the theme as:

Text(
  "Hello world",
  style: MyCustomThemeData.of(context).specialTextStyle,
),

and the theme dos apply. But how can I access them using the following code within the build() method? (The Theme.of(context) method of access is still not working!)

MyCustomAppTheme? mcat = Theme.of(context).extension<MyCustomAppTheme>();
TextStyle myTextStyle = mcat.specialTextStyle;
2
  • 1
    check this sample Commented Feb 19 at 8:00
  • @pskink, thank you that was helpful. Commented Feb 19 at 8:28

1 Answer 1

1

Note: Improvements to this answer as separate answers or comments are welcome. I am not marking my answer as accepted because there might be better solution with others. Currently I'm not satisfied with how I'm having to null check and assign a default value as:

TextStyle mcat02 =
Theme.of(context).myCustomThemeData?.specialTextStyle ??
TextStyle().copyWith(color: Colors.green);

Update, to above, I've learned that we can type-case the right hand side as follows (which seems simpler):

TextStyle mcat03 =
Theme.of(context).myCustomThemeData?.specialTextStyle as TextStyle;

I managed to get things working. I am posting it as answer for documentation purpose, if it might help others.

In this main.dart file, in one of the Text widget I've directly used the class MyCustomThemeData and consequently imported import 'package:extending_flutter_themedata/themes/extending_themedata/my_custom_themedata.dart'; which is not how we want to access the theme. The alternative method shown using the TextStyle mcat02 = ... ... is the preferred way.

The theme for both light and dark modes apply. We may check them with the following command in the terminal:

adb shell "cmd uimode night yes" // dark mode
adb shell "cmd uimode night no" // light mode

main.dart:

import 'package:extending_flutter_themedata/themes/extending_themedata/my_custom_themedata.dart';
import 'package:extending_flutter_themedata/themes/theme.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      themeMode: ThemeMode.system,
      theme: MyCustomAppTheme.lightTheme,
      darkTheme: MyCustomAppTheme.darkTheme,
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    /*
    // Could not get the following working:
    MyCustomAppTheme? mcat = Theme.of(context).extension<MyCustomAppTheme>();
    TextStyle myTextStyle = mcat.specialTextStyle;
    */

    // This one works:
    TextStyle mcat02 =
        Theme.of(context).myCustomThemeData?.specialTextStyle ??
        TextStyle().copyWith(color: Colors.green);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).primaryColor,
        title: const Text("Extending ThemeData"),
      ),
      body: SingleChildScrollView(
        child: Padding(
          padding: EdgeInsets.all(15.0),
          child: Column(
            children: [
              Text(
                "Hello world",
                style: MyCustomThemeData.of(context).specialTextStyle,
              ),
              Text("Hello world", style: mcat02),
              Text("Hello world", style: mcat02.copyWith(color: Colors.pink)),
            ],
          ),
        ),
      ),
    );
  }
}

theme.dart:

import 'package:extending_flutter_themedata/themes/extending_themedata/my_custom_themedata.dart';
import 'package:flutter/material.dart';

class MyCustomAppTheme {
  MyCustomAppTheme._();

  static ThemeData lightTheme = ThemeData(
    useMaterial3: true,
    fontFamily: "Roboto",
    brightness: Brightness.light,
    extensions: <ThemeExtension<dynamic>>[
      MyCustomThemeData(
        customBackgroundColor: Colors.pink,
        specialTextStyle: const TextStyle(
          fontSize: 30.0,
          fontWeight: FontWeight.bold,
          color: Colors.amber,
        ),
      ),
    ],
  );

  static ThemeData darkTheme = ThemeData(
    useMaterial3: true,
    fontFamily: "Roboto",
    brightness: Brightness.dark,
    extensions: <ThemeExtension<dynamic>>[
      MyCustomThemeData(
        customBackgroundColor: Colors.pink,
        specialTextStyle: const TextStyle(
          fontSize: 30.0,
          fontWeight: FontWeight.bold,
          color: Colors.tealAccent,
        ),
      ),
    ],
  );
}

my_custom_themedata.dart:

import 'package:flutter/material.dart';

class MyCustomThemeData extends ThemeExtension<MyCustomThemeData> {
  final Color customBackgroundColor;
  final TextStyle specialTextStyle;

  MyCustomThemeData({
    this.customBackgroundColor = Colors.blue,
    this.specialTextStyle = const TextStyle(),
  });

  @override
  MyCustomThemeData copyWith({
    Color? customBackgroundColor,
    TextStyle? specialTextStyle,
  }) {
    return MyCustomThemeData(
      customBackgroundColor:
          customBackgroundColor ?? this.customBackgroundColor,
      specialTextStyle: specialTextStyle ?? this.specialTextStyle,
    );
  }

  static MyCustomThemeData of(BuildContext context) {
    return Theme.of(context).extension<MyCustomThemeData>()!;
  }

  @override
  MyCustomThemeData lerp(ThemeExtension<MyCustomThemeData>? other, double t) {
    if (other is! MyCustomThemeData) {
      return this;
    }

    return MyCustomThemeData(
      customBackgroundColor:
          Color.lerp(customBackgroundColor, other.customBackgroundColor, t)!,
      specialTextStyle:
          TextStyle.lerp(specialTextStyle, other.specialTextStyle, t)!,
    );
  }

  static const id = 'my_custom_theme_data';
}

extension ThemeDataExtension on ThemeData {
  MyCustomThemeData? get myCustomThemeData => extension<MyCustomThemeData>();

  ThemeData copyWithCustom({MyCustomThemeData? myCustomThemeData}) {
    return copyWith(
      extensions: <ThemeExtension<dynamic>>{
        if (myCustomThemeData != null) myCustomThemeData,
      },
    );
  }

  /*

  static ThemeData lerp(ThemeData? a, ThemeData? b, double t) {
    return ThemeData.lerp(a, b, t)?.copyWithCustom(
      myCustomThemeData: MyCustomThemeData.lerp(
        a?.extension<MyCustomThemeData>(),
        b?.extension<MyCustomThemeData>(),
        t,
      ),
    );
  }

  */
}

In my_custom_themedata.dart, I am just commenting out the static ThemeData lerp definition. Any answer fixing are welcome.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.