In Flutter, creating a visually appealing and user-friendly application often involves providing options for users to customize the app’s theme. Dynamic theming allows users to switch between light and dark modes or choose from a range of color schemes, enhancing their overall experience. This blog post will guide you through implementing dynamic themes in Flutter using the Provider package.
What is Dynamic Theming?
Dynamic theming refers to the ability of an application to change its visual appearance (themes, colors, styles) in response to user preferences or system settings, such as dark mode.
Why Use Dynamic Theming?
- Improved User Experience: Allows users to personalize the app to their preferences.
- Accessibility: Supports users with visual impairments by offering high-contrast themes.
- Aesthetic Appeal: Keeps the app modern and engaging.
Implementation Overview
We will use the Provider package to manage and provide the app’s theme data. Here’s a step-by-step guide to setting up dynamic themes:
Step 1: Add Dependencies
First, add the provider
package to your pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0
Run flutter pub get
to install the dependencies.
Step 2: Create a ThemeProvider Class
Create a ThemeProvider
class that extends ChangeNotifier
to manage and notify theme changes.
import 'package:flutter/material.dart';
class ThemeProvider extends ChangeNotifier {
ThemeMode themeMode;
ThemeProvider({this.themeMode = ThemeMode.system});
bool get isDarkMode => themeMode == ThemeMode.dark;
void toggleTheme(bool isOn) {
themeMode = isOn ? ThemeMode.dark : ThemeMode.light;
notifyListeners();
}
}
Step 3: Define ThemeData
Create your ThemeData
for both light and dark modes.
import 'package:flutter/material.dart';
class AppThemes {
static final light = ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
);
static final dark = ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.grey,
foregroundColor: Colors.white,
),
);
}
Step 4: Wrap Your App with ChangeNotifierProvider
Wrap your app’s MaterialApp
widget with ChangeNotifierProvider
to provide the ThemeProvider
.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_provider.dart'; // Import the ThemeProvider
import 'app_themes.dart'; // Import your AppThemes
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => ThemeProvider(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeProvider = Provider.of(context);
return MaterialApp(
title: 'Dynamic Theme Demo',
themeMode: themeProvider.themeMode,
theme: AppThemes.light,
darkTheme: AppThemes.dark,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dynamic Theme Demo'),
),
body: Center(
child: ThemeSwitcher(),
),
);
}
}
Step 5: Create a Theme Switcher Widget
Create a widget to toggle between light and dark themes using a Switch
widget.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_provider.dart'; // Import your ThemeProvider
class ThemeSwitcher extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeProvider = Provider.of(context);
return Switch(
value: themeProvider.isDarkMode,
onChanged: (value) {
final provider = Provider.of(context, listen: false);
provider.toggleTheme(value);
},
);
}
}
Complete Example
Here’s the complete example combining all the steps above:
main.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_provider.dart';
import 'app_themes.dart';
import 'theme_switcher.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => ThemeProvider(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeProvider = Provider.of(context);
return MaterialApp(
title: 'Dynamic Theme Demo',
themeMode: themeProvider.themeMode,
theme: AppThemes.light,
darkTheme: AppThemes.dark,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dynamic Theme Demo'),
),
body: Center(
child: ThemeSwitcher(),
),
);
}
}
theme_provider.dart:
import 'package:flutter/material.dart';
class ThemeProvider extends ChangeNotifier {
ThemeMode themeMode;
ThemeProvider({this.themeMode = ThemeMode.system});
bool get isDarkMode => themeMode == ThemeMode.dark;
void toggleTheme(bool isOn) {
themeMode = isOn ? ThemeMode.dark : ThemeMode.light;
notifyListeners();
}
}
app_themes.dart:
import 'package:flutter/material.dart';
class AppThemes {
static final light = ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
);
static final dark = ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
appBarTheme: const AppBarTheme(
backgroundColor: Colors.grey,
foregroundColor: Colors.white,
),
);
}
theme_switcher.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_provider.dart';
class ThemeSwitcher extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeProvider = Provider.of(context);
return Switch(
value: themeProvider.isDarkMode,
onChanged: (value) {
final provider = Provider.of(context, listen: false);
provider.toggleTheme(value);
},
);
}
}
Customization and Advanced Usage
- Theme Color Palette: Define and switch between different color palettes beyond light and dark modes.
- Persistence: Store the user’s theme preference locally using
shared_preferences
or a similar package so that the app remembers the preference across sessions. - System Theme: Automatically switch to dark mode when the system-wide setting changes (using
ThemeMode.system
).
Conclusion
Implementing dynamic themes in Flutter using the Provider package provides a clean and efficient way to allow users to customize the appearance of your app. By managing the theme data in a ChangeNotifier
and providing it to your app’s widgets, you can create a flexible and user-friendly theming system that enhances the overall user experience.