You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter主题切换异常:DropdownButton断言错误问题求助

主题切换DropdownButton断言错误排查与修复

问题现象

实现主题切换功能时,选择系统主题或自定义主题均触发断言错误,错误信息如下:

_AssertionError ('package:flutter/src/material/dropdown.dart': Failed assertion: line 888 pos 15: 'items == null || items.isEmpty || value == null ||
              items.where((DropdownMenuItem<T> item) {
                return item.value == value;
              }).length == 1': There should be exactly one item with [DropdownButton]'s value: ThemeData#69a95. 
Either zero or 2 or more [DropdownMenuItem]s were detected with the same value)

问题代码

主题切换下拉组件

Widget themeSwitcherDropdown() {
  return Consumer<ThemeManager>(
    builder: (cont, themeManager, _) {
      return DropdownButton<ThemeData>(
        value: themeManager.currentTheme,
        onChanged: (newTheme) {
          themeManager.setTheme(newTheme!);
        },
        items: themeManager.getAvalibleThemes().entries.map((entry) {
          final name = entry.key;
          final theme = entry.value;

          return DropdownMenuItem<ThemeData>(
            value: theme,
            child: Text(name),
          );
        }).toList(),
      );
    },
  );
}

ThemeManager代码

class ThemeManager extends ChangeNotifier {
  BuildContext context;

  ThemeManager(this.context);

  ThemeData _currentTheme = ThemeData.light();
  ThemeData get currentTheme => _currentTheme;

  Map<String, ThemeData> getAvalibleThemes() {
    Map<String, ThemeData> availableThemes = {
      "Light": ThemeData.light(useMaterial3: true),
      "Dark": ThemeData.dark(useMaterial3: true),
      "System": ThemeData.light(),
      // Add more themes as needed
    };
    return availableThemes;
  }

  ThemeData getSystemTheme() {
    Brightness brightness = Theme.of(context).brightness;
    if (brightness == Brightness.dark) {
      return ThemeData.dark(useMaterial3: true).copyWith(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple));
    } else {
      return ThemeData.light(useMaterial3: true).copyWith(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple));
    }
  }

  void setTheme(ThemeData theme) {
    if (theme == ThemeData.light()) {
      theme = getSystemTheme();
    }
    _currentTheme = theme;
    notifyListeners();
  }
}

错误原因

  1. DropdownButton值不匹配:选择"System"选项后,setTheme会将_currentTheme替换为getSystemTheme()返回的自定义ThemeData实例,但该实例不在DropdownButton的items列表中(items里的"System"对应原始ThemeData.light()),导致DropdownButton找不到匹配的item,触发断言错误。
  2. ThemeData实例比较失效:ThemeData是复杂对象,每次调用构造函数(如ThemeData.light())都会生成新实例,直接用==比较永远返回false,导致替换系统主题的逻辑根本不会执行。

修复方案

改用枚举类型跟踪主题类型,避免直接操作ThemeData对象,确保DropdownButton的value始终在选项范围内。

步骤1:定义主题类型枚举

enum ThemeType { light, dark, system }

步骤2:重构ThemeManager

class ThemeManager extends ChangeNotifier {
  final BuildContext context;
  ThemeType _currentThemeType = ThemeType.light;
  ThemeType get currentThemeType => _currentThemeType;

  ThemeData _currentTheme = ThemeData.light();
  ThemeData get currentTheme => _currentTheme;

  ThemeManager(this.context) {
    _currentTheme = _getThemeForType(_currentThemeType);
  }

  // 获取主题选项(名称+类型)
  Map<String, ThemeType> getAvailableThemeOptions() {
    return {
      "Light": ThemeType.light,
      "Dark": ThemeType.dark,
      "System": ThemeType.system,
    };
  }

  // 根据类型获取对应ThemeData
  ThemeData _getThemeForType(ThemeType type) {
    switch (type) {
      case ThemeType.light:
        return ThemeData.light(useMaterial3: true);
      case ThemeType.dark:
        return ThemeData.dark(useMaterial3: true);
      case ThemeType.system:
        return _getSystemTheme();
    }
  }

  // 获取系统主题(改用MediaQuery获取系统真实亮度)
  ThemeData _getSystemTheme() {
    Brightness brightness = MediaQuery.of(context).platformBrightness;
    if (brightness == Brightness.dark) {
      return ThemeData.dark(useMaterial3: true).copyWith(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
      );
    } else {
      return ThemeData.light(useMaterial3: true).copyWith(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      );
    }
  }

  // 设置主题(接收ThemeType)
  void setTheme(ThemeType type) {
    _currentThemeType = type;
    _currentTheme = _getThemeForType(type);
    notifyListeners();
  }

  // 可选:监听系统主题变化,自动更新
  void listenToSystemThemeChanges() {
    WidgetsBinding.instance.addObserver(_SystemThemeObserver(this));
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(_SystemThemeObserver(this));
    super.dispose();
  }
}

// 系统主题变化监听类
class _SystemThemeObserver extends WidgetsBindingObserver {
  final ThemeManager themeManager;

  _SystemThemeObserver(this.themeManager);

  @override
  void didChangePlatformBrightness() {
    if (themeManager.currentThemeType == ThemeType.system) {
      themeManager._currentTheme = themeManager._getSystemTheme();
      themeManager.notifyListeners();
    }
  }
}

步骤3:重构下拉组件

Widget themeSwitcherDropdown() {
  return Consumer<ThemeManager>(
    builder: (context, themeManager, _) {
      return DropdownButton<ThemeType>(
        value: themeManager.currentThemeType,
        onChanged: (newType) {
          if (newType != null) {
            themeManager.setTheme(newType);
          }
        },
        items: themeManager.getAvailableThemeOptions().entries.map((entry) {
          final name = entry.key;
          final type = entry.value;
          return DropdownMenuItem<ThemeType>(
            value: type,
            child: Text(name),
          );
        }).toList(),
      );
    },
  );
}

修复说明

  • 用ThemeType枚举作为DropdownButton的value类型,确保value始终存在于items选项中,彻底解决断言错误。
  • 通过枚举映射到对应的ThemeData,避免直接操作复杂对象,解决实例比较失效的问题。
  • 改用MediaQuery.of(context).platformBrightness获取系统真实亮度,确保系统主题跟随正确。

内容的提问来源于stack exchange,提问作者RitomG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 23:29:50