# How ChatMCP Handles Theme Switching Between Light, Dark, and System Modes

> Discover how ChatMCP effortlessly manages theme switching for light, dark, and system modes. Learn about its Provider architecture and SharedPreferences persistence.

- Repository: [刀刀/chatmcp](https://github.com/daodao97/chatmcp)
- Tags: how-to-guide
- Published: 2026-02-28

---

**ChatMCP implements theme switching through a Provider-based architecture that persists user preferences to SharedPreferences, exposes a dropdown selector in the General Settings UI, and applies the selected mode via Flutter's MaterialApp themeMode property.**

ChatMCP is an open-source Flutter client for Model Context Protocol (MCP) servers that provides a customizable chat interface. Theme switching in ChatMCP allows users to toggle between light, dark, and system-default appearances, with preferences persisted across app restarts. The implementation relies on Provider state management and Flutter's built-in theming capabilities to deliver a reactive user experience.

## Persistent Storage with SharedPreferences

Theme preferences in ChatMCP are stored as part of the `GeneralSetting` model and persisted using **SharedPreferences**. The default theme is set to `light` when no prior selection exists.

In `lib/provider/settings_provider.dart`, the theme field is defined within the `GeneralSetting` class (lines 88‑96). The provider exposes the `updateGeneralSettingsPartially` method (lines 14‑28) to modify this value asynchronously. When called, this method updates the in-memory model, encodes it to JSON, and writes it to SharedPreferences under the key `generalSettings`.

```dart
Future<void> updateGeneralSettingsPartially({String? theme, …}) async {
  final prefs = await SharedPreferences.getInstance();

  _generalSetting = GeneralSetting(
    theme: theme ?? _generalSetting.theme,
    // other fields unchanged …
  );
  await prefs.setString('generalSettings', jsonEncode(_generalSetting.toJson()));
  notifyListeners();
}

```

The `notifyListeners()` call triggers a rebuild in any widget consuming this provider, ensuring the UI reflects the new preference immediately.

## Theme Selection UI

The settings interface allows users to select their preferred theme through a dropdown in the General Settings screen. Located in `lib/page/setting/general_setting.dart` (lines 24‑64), the UI uses a `DropdownButtonFormField` bound to the current value from `SettingsProvider`.

When a user selects a new option, the widget invokes `settings.updateGeneralSettingsPartially(theme: value)` to persist the change:

```dart
DropdownButtonFormField<String>(
  value: settings.generalSetting.theme,
  items: const [
    DropdownMenuItem(value: 'light',  child: CText(text: l10n.lightTheme)),
    DropdownMenuItem(value: 'dark',   child: CText(text: l10n.darkTheme)),
    DropdownMenuItem(value: 'system', child: CText(text: l10n.followSystem)),
  ],
  onChanged: (value) {
    if (value != null) {
      settings.updateGeneralSettingsPartially(theme: value);
    }
  },
);

```

This pattern decouples the UI from storage logic, delegating persistence concerns to the provider while the widget focuses purely on presentation and user input handling.

## Applying Themes at the Application Root

The root `MyApp` widget in `lib/main.dart` (lines 77‑90) consumes `SettingsProvider` via a `Consumer` widget and applies the theme using Flutter's `MaterialApp` properties. The implementation provides distinct `ThemeData` configurations for light and dark modes, while the `themeMode` property determines which theme is active.

A private helper method `_getThemeMode` (lines 5‑14) maps the stored string values to Flutter's `ThemeMode` enum:

```dart
return Consumer<SettingsProvider>(
  builder: (context, settings, child) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true, brightness: Brightness.light),
      darkTheme: ThemeData(useMaterial3: true, brightness: Brightness.dark),
      themeMode: _getThemeMode(settings.generalSetting.theme),
      // …
    );
  },
);

ThemeMode _getThemeMode(String theme) {
  switch (theme) {
    case 'light':  return ThemeMode.light;
    case 'dark':   return ThemeMode.dark;
    case 'system':
    default:       return ThemeMode.system;
  }
}

```

When the user selects **System**, `ThemeMode.system` instructs Flutter to automatically follow the platform's brightness setting without requiring additional platform channel code.

## Programmatic Theme Changes

For testing or automated configuration, themes can be changed programmatically by invoking the provider method directly:

```dart
await SettingsProvider().updateGeneralSettingsPartially(theme: 'dark');

```

This bypasses the UI layer while maintaining the same persistence and notification guarantees.

## Summary

- **Storage Layer**: `lib/provider/settings_provider.dart` manages the `GeneralSetting.theme` field, persists values to SharedPreferences, and notifies listeners of changes.
- **UI Layer**: `lib/page/setting/general_setting.dart` provides a `DropdownButtonFormField` that calls `updateGeneralSettingsPartially()` when selections change.
- **Application Layer**: `lib/main.dart` consumes the provider, maps string values to `ThemeMode` via `_getThemeMode()`, and supplies `ThemeData` to `MaterialApp`.
- **System Integration**: Selecting `system` delegates brightness detection to Flutter's built-in platform brightness handling.

## Frequently Asked Questions

### Where does ChatMCP store the selected theme preference?

ChatMCP stores the theme preference in **SharedPreferences** as a JSON-encoded string under the key `generalSettings`. The `SettingsProvider` class in `lib/provider/settings_provider.dart` handles serialization and deserialization of the `GeneralSetting` object, ensuring the theme persists across app restarts.

### What happens when a user selects "System" theme mode?

When the user selects `system`, the `_getThemeMode` helper in `lib/main.dart` returns `ThemeMode.system`. Flutter then automatically matches the app's brightness to the operating system's current setting (light or dark) without requiring manual platform checks or additional code in ChatMCP.

### How does the UI update immediately after changing the theme?

The `SettingsProvider` class extends Flutter's `ChangeNotifier`. When `updateGeneralSettingsPartially()` is called, it invokes `notifyListeners()` after persisting the new value. The root `MyApp` widget wraps `MaterialApp` in a `Consumer<SettingsProvider>`, which rebuilds the entire app widget tree when the provider notifies listeners, applying the new `ThemeMode` instantly.

### Can the theme be changed from outside the settings screen?

Yes. Any component with access to the `SettingsProvider` instance can change the theme programmatically by calling `updateGeneralSettingsPartially(theme: 'dark')` (or `light`/`system`). This updates the persisted preference and triggers a global UI rebuild, though the primary interface for this action is the dropdown in the General Settings page.