Keywords: Flutter | Theme Switching | Provider | Persistent Storage | Dark Mode
Abstract: This article provides a comprehensive technical solution for implementing dark and light theme modes in Flutter applications. By combining Provider state management with SharedPreferences persistent storage, it achieves real-time theme switching and long-term user preference preservation. The article covers theme detection, state management, and interface updates with complete code examples and implementation logic, helping developers build modern Flutter applications with theme switching capabilities.
Core Architecture of Theme Mode Implementation
Implementing theme switching functionality in Flutter applications requires comprehensive consideration of state management, persistent storage, and interface updates. The Provider-based state management solution effectively addresses the propagation of theme data throughout the widget tree, while SharedPreferences ensures long-term preservation of user theme preferences.
Implementation of Storage Management Layer
Persistent storage forms the foundation of theme mode implementation, achieved through the SharedPreferences package for local data storage. The storage manager needs to handle different types of data storage requirements:
import 'package:shared_preferences/shared_preferences.dart';
class StorageManager {
static void saveData(String key, dynamic value) async {
final prefs = await SharedPreferences.getInstance();
if (value is int) {
prefs.setInt(key, value);
} else if (value is String) {
prefs.setString(key, value);
} else if (value is bool) {
prefs.setBool(key, value);
} else {
print("Invalid Type");
}
}
static Future<dynamic> readData(String key) async {
final prefs = await SharedPreferences.getInstance();
dynamic obj = prefs.get(key);
return obj;
}
static Future<bool> deleteData(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.remove(key);
}
}
Design and Implementation of Theme Manager
The theme manager, acting as a ChangeNotifier, is responsible for managing the application's theme state and providing theme switching functionality. Its core lies in defining specific configurations for dark and light themes, and driving interface updates through notification mechanisms:
import 'package:flutter/material.dart';
import '../services/storage_manager.dart';
class ThemeNotifier with ChangeNotifier {
final darkTheme = ThemeData(
primarySwatch: Colors.grey,
primaryColor: Colors.black,
brightness: Brightness.dark,
backgroundColor: const Color(0xFF212121),
accentColor: Colors.white,
accentIconTheme: IconThemeData(color: Colors.black),
dividerColor: Colors.black12,
);
final lightTheme = ThemeData(
primarySwatch: Colors.grey,
primaryColor: Colors.white,
brightness: Brightness.light,
backgroundColor: const Color(0xFFE5E5E5),
accentColor: Colors.black,
accentIconTheme: IconThemeData(color: Colors.white),
dividerColor: Colors.white54,
);
ThemeData _themeData;
ThemeData getTheme() => _themeData;
ThemeNotifier() {
StorageManager.readData('themeMode').then((value) {
print('value read from storage: ' + value.toString());
var themeMode = value ?? 'light';
if (themeMode == 'light') {
_themeData = lightTheme;
} else {
print('setting dark theme');
_themeData = darkTheme;
}
notifyListeners();
});
}
void setDarkMode() async {
_themeData = darkTheme;
StorageManager.saveData('themeMode', 'dark');
notifyListeners();
}
void setLightMode() async {
_themeData = lightTheme;
StorageManager.saveData('themeMode', 'light');
notifyListeners();
}
}
Application Entry and Provider Integration
At the application entry point, the theme manager is injected into the widget tree through ChangeNotifierProvider, ensuring that the entire application can access the theme state:
void main() {
return runApp(ChangeNotifierProvider<ThemeNotifier>(
create: (_) => new ThemeNotifier(),
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<ThemeNotifier>(
builder: (context, theme, _) => MaterialApp(
theme: theme.getTheme(),
home: Scaffold(
appBar: AppBar(
title: Text('Hybrid Theme'),
),
body: Row(
children: [
Container(
child: FlatButton(
onPressed: () => {
print('Set Light Theme');
theme.setLightMode();
},
child: Text('Set Light Theme'),
),
),
Container(
child: FlatButton(
onPressed: () => {
print('Set Dark theme');
theme.setDarkMode();
},
child: Text('Set Dark theme'),
),
),
],
),
),
),
);
}
}
User Interaction Design for Theme Switching
Theme switching operations are triggered through button components. When users click the corresponding buttons, the theme manager's corresponding methods are called, triggering state updates and interface reconstruction:
FlatButton(
onPressed: () => {
print('Set Light Theme');
theme.setLightMode();
},
child: Text('Set Light Theme'),
)
Supplementary Solution for System Theme Detection
In addition to in-app theme switching, system theme detection functionality can be integrated. By observing system brightness changes through WidgetsBindingObserver, synchronization with the system theme can be achieved:
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Brightness? _brightness;
@override
void initState() {
WidgetsBinding.instance?.addObserver(this);
_brightness = WidgetsBinding.instance?.window.platformBrightness;
super.initState();
}
@override
void didChangePlatformBrightness() {
if (mounted) {
setState(() {
_brightness = WidgetsBinding.instance?.window.platformBrightness;
});
}
super.didChangePlatformBrightness();
}
}
Extensibility of Custom Theme Configuration
In practical development, theme configurations can be extended according to specific requirements. Beyond basic color settings, visual elements such as fonts, shapes, and animations can be defined to create unique brand visual experiences:
final customDarkTheme = ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.deepPurple,
accentColor: Colors.amber,
fontFamily: 'Roboto',
textTheme: TextTheme(
headline1: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
bodyText1: TextStyle(fontSize: 16, color: Colors.white70),
),
);
Performance Optimization and Best Practices
When implementing theme switching functionality, attention must be paid to performance optimization. Avoid rebuilding the entire widget tree during theme switching by employing proper component design and state management to ensure smooth application operation. Additionally, provide appropriate loading states and error handling to enhance user experience.