How to get the context in a widget without a Build method

Issue

I have a Theme class that has hard coded font sizes, icon sizes, radius values, etc that I want to be dynamic so they can be responsive to different screen sizes. I haven’t been able to figure out how to use MediaQuery in my Theme class because it requires a context. Is there a way to get context in my Theme class? Thanks for your help.

class MyApp extends StatelessWidget {
  const MyApp({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<MenuInfoProvider>(
          create: (context) => MenuInfoProvider(
            MenuName.homePage,
            menuIcon: Icons.home,
            menuText: MenuLabelString.home,
          ),
        ),
        ChangeNotifierProvider<ThemeProvider>(
          create: (context) => ThemeProvider(),
        ),
        ChangeNotifierProvider<UserProvider>(
            create: (context) => UserProvider()),
        Provider<Database>(
          create: (context) => FirestoreDatabase(
            uid: Auth().currentUser!.uid,
          ),
        ),
        Provider<AuthBase>(
          create: (context) => Auth(),
        ),
      ],
      builder: (context, _) {
        final themeProvider = Provider.of<ThemeProvider>(context);

        return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: AppConstants.improvePractice,
          theme: ThemeProvider.lightTheme,
          darkTheme: ThemeProvider.darkTheme,
          themeMode: themeProvider.currentTheme,
          initialRoute: RouteStrings.landing,
          onGenerateRoute: RouteGenerator.generateRoute,
        );
      },
    );
  }
}

This is some of the code from my Theme class. The dark theme which is excluded is similar to the light theme with different colors.

class ThemeProvider extends ChangeNotifier {
  ThemeMode currentTheme = ThemeMode.system;

  bool get isDarkMode => currentTheme == ThemeMode.dark;

  void toggleTheme({required bool isOn}) {
    currentTheme = isOn ? ThemeMode.dark : ThemeMode.light;
    notifyListeners();
  }

  static ThemeData get lightTheme {
    return ThemeData(
      appBarTheme: AppBarTheme(
        backgroundColor: AppColors.purple,
        elevation: 4.0,
        iconTheme: IconThemeData(
          color: AppColors.brandWhite.withOpacity(
            0.87,
          ),
          size: 24.0,
        ),
        toolbarTextStyle: TextStyle(
          color: AppColors.brandWhite.withOpacity(0.87),
          fontFamily: AppTextStyle.robotoSlabFont,
          fontSize: 20.0,
          fontWeight: FontWeight.w500,
          letterSpacing: 0.15,
        ),
      ),
      elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
          alignment: Alignment.center,
          elevation: 2.0,
          minimumSize: const Size(
            64.0,
            36.0,
          ),
          onPrimary: AppColors.brandWhite.withOpacity(
            0.87,
          ),
          padding: const EdgeInsets.symmetric(
            horizontal: 16.0,
            vertical: 8.0,
          ),
          primary: AppColors.purple,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(
              8.0,
            ),
          ),
          textStyle: AppTextStyle.button,
        ),
      ),
    );
  }
}

Solution

I think you could replace static ThemeData get lightTheme by a static method that takes a BuildContext parameter instead, Something like:

class ThemeProvider extends ChangeNotifier {
  ThemeMode currentTheme = ThemeMode.system;

  bool get isDarkMode => currentTheme == ThemeMode.dark;

  void toggleTheme({required bool isOn}) {
    currentTheme = isOn ? ThemeMode.dark : ThemeMode.light;
    notifyListeners();
  }

  static ThemeData getLightTheme(BuildContext context) {
    
    // Dynamic elevation depending on device Height.
    final elevation = MediaQuery.of(context).size.height <600 ?4:6;

    return ThemeData(
      appBarTheme: AppBarTheme(
        backgroundColor: AppColors.purple,
        elevation: elevation,
        iconTheme: IconThemeData(
          color: AppColors.brandWhite.withOpacity(
            0.87,
          ),
          size: 24.0,
        ),
        toolbarTextStyle: TextStyle(
          color: AppColors.brandWhite.withOpacity(0.87),
          fontFamily: AppTextStyle.robotoSlabFont,
          fontSize: 20.0,
          fontWeight: FontWeight.w500,
          letterSpacing: 0.15,
        ),
      ),
      elevatedButtonTheme: ElevatedButtonThemeData(
        style: ElevatedButton.styleFrom(
          alignment: Alignment.center,
          elevation: 2.0,
          minimumSize: const Size(
            64.0,
            36.0,
          ),
          onPrimary: AppColors.brandWhite.withOpacity(
            0.87,
          ),
          padding: const EdgeInsets.symmetric(
            horizontal: 16.0,
            vertical: 8.0,
          ),
          primary: AppColors.purple,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(
              8.0,
            ),
          ),
          textStyle: AppTextStyle.button,
        ),
      ),
    );
  }
}

Answered By – Boris Kayi

Answer Checked By – David Goodson (FlutterFixes Volunteer)

Leave a Reply

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