← Назад к вопросам

Реализовать экран настроек с SharedPreferences

1.0 Junior🔥 111 комментариев
#Flutter виджеты#Хранение данных

Условие

Создайте экран настроек приложения с сохранением в SharedPreferences.

Требования

  1. Переключатель темной темы
  2. Выбор языка (из списка)
  3. Переключатель уведомлений
  4. Слайдер размера шрифта
  5. Сохранение всех настроек в SharedPreferences
  6. Применение настроек при запуске приложения

Дополнительные баллы

  • Группировка настроек по категориям
  • Кнопка сброса к настройкам по умолчанию
  • Preview изменений в реальном времени
  • Экспорт/импорт настроек

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение: Экран Настроек с SharedPreferences

Полнофункциональный экран настроек с группировкой, предпросмотром и сохранением.

Модель настроек

class AppSettings {
  final bool darkMode;
  final String language;
  final bool notificationsEnabled;
  final double fontSize;

  AppSettings({
    this.darkMode = false,
    this.language = 'ru',
    this.notificationsEnabled = true,
    this.fontSize = 1.0,
  });

  Map<String, dynamic> toJson() {
    return {
      'darkMode': darkMode,
      'language': language,
      'notificationsEnabled': notificationsEnabled,
      'fontSize': fontSize,
    };
  }

  factory AppSettings.fromJson(Map<String, dynamic> json) {
    return AppSettings(
      darkMode: json['darkMode'] ?? false,
      language: json['language'] ?? 'ru',
      notificationsEnabled: json['notificationsEnabled'] ?? true,
      fontSize: json['fontSize'] ?? 1.0,
    );
  }
}

Сервис настроек

class SettingsService {
  static const String _key = 'app_settings';

  final SharedPreferences _prefs;

  SettingsService(this._prefs);

  Future<AppSettings> loadSettings() async {
    final settingsJson = _prefs.getString(_key);
    if (settingsJson != null) {
      return AppSettings.fromJson(jsonDecode(settingsJson));
    }
    return AppSettings();
  }

  Future<void> saveSettings(AppSettings settings) async {
    await _prefs.setString(_key, jsonEncode(settings.toJson()));
  }

  Future<void> resetToDefaults() async {
    await _prefs.remove(_key);
  }

  Future<String> exportSettings() async {
    final settings = await loadSettings();
    return jsonEncode(settings.toJson());
  }

  Future<void> importSettings(String jsonString) async {
    try {
      final json = jsonDecode(jsonString);
      final settings = AppSettings.fromJson(json);
      await saveSettings(settings);
    } catch (e) {
      throw Exception('Ошибка импорта настроек');
    }
  }
}

Контроллер настроек

class SettingsController extends GetxController {
  final SettingsService _settingsService;

  final settings = AppSettings().obs;
  final isLoading = false.obs;

  SettingsController({required SettingsService settingsService})
      : _settingsService = settingsService;

  @override
  void onInit() {
    super.onInit();
    loadSettings();
  }

  Future<void> loadSettings() async {
    isLoading.value = true;
    try {
      final loadedSettings = await _settingsService.loadSettings();
      settings.value = loadedSettings;
    } catch (e) {
      Get.snackbar('Ошибка', 'Не удалось загрузить настройки');
    } finally {
      isLoading.value = false;
    }
  }

  Future<void> setDarkMode(bool value) async {
    final newSettings = AppSettings(
      darkMode: value,
      language: settings.value.language,
      notificationsEnabled: settings.value.notificationsEnabled,
      fontSize: settings.value.fontSize,
    );
    settings.value = newSettings;
    await _settingsService.saveSettings(newSettings);
  }

  Future<void> setLanguage(String language) async {
    final newSettings = AppSettings(
      darkMode: settings.value.darkMode,
      language: language,
      notificationsEnabled: settings.value.notificationsEnabled,
      fontSize: settings.value.fontSize,
    );
    settings.value = newSettings;
    await _settingsService.saveSettings(newSettings);
  }

  Future<void> setNotifications(bool value) async {
    final newSettings = AppSettings(
      darkMode: settings.value.darkMode,
      language: settings.value.language,
      notificationsEnabled: value,
      fontSize: settings.value.fontSize,
    );
    settings.value = newSettings;
    await _settingsService.saveSettings(newSettings);
  }

  Future<void> setFontSize(double value) async {
    final newSettings = AppSettings(
      darkMode: settings.value.darkMode,
      language: settings.value.language,
      notificationsEnabled: settings.value.notificationsEnabled,
      fontSize: value,
    );
    settings.value = newSettings;
    await _settingsService.saveSettings(newSettings);
  }

  Future<void> resetToDefaults() async {
    await _settingsService.resetToDefaults();
    settings.value = AppSettings();
    Get.snackbar('Успех', 'Настройки сброшены');
  }

  Future<void> exportSettings() async {
    try {
      final json = await _settingsService.exportSettings();
      // Копируем в буфер обмена
      await Clipboard.setData(ClipboardData(text: json));
      Get.snackbar('Успех', 'Настройки скопированы');
    } catch (e) {
      Get.snackbar('Ошибка', 'Не удалось экспортировать');
    }
  }

  Future<void> importSettings(String json) async {
    try {
      await _settingsService.importSettings(json);
      await loadSettings();
      Get.snackbar('Успех', 'Настройки импортированы');
    } catch (e) {
      Get.snackbar('Ошибка', 'Не удалось импортировать');
    }
  }
}

Главная страница настроек

class SettingsPage extends StatefulWidget {
  @override
  _SettingsPageState createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  final SettingsController _controller = Get.put(
    SettingsController(settingsService: Get.find<SettingsService>()),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Настройки'),
        elevation: 0,
        backgroundColor: Color(0xFF667EEA),
      ),
      body: Obx(() {
        if (_controller.isLoading.value) {
          return Center(child: CircularProgressIndicator());
        }

        return SingleChildScrollView(
          child: Column(
            children: [
              _buildThemeSection(),
              _buildLanguageSection(),
              _buildNotificationSection(),
              _buildFontSizeSection(),
              _buildPreviewSection(),
              _buildActionSection(),
              SizedBox(height: 24),
            ],
          ),
        );
      }),
    );
  }

  Widget _buildThemeSection() {
    return _buildSettingSection(
      title: 'Внешний вид',
      icon: Icons.palette,
      children: [
        Obx(() {
          return SwitchListTile(
            title: Text('Тёмная тема'),
            subtitle: Text(
              _controller.settings.value.darkMode
                  ? 'Включена'
                  : 'Выключена',
            ),
            value: _controller.settings.value.darkMode,
            onChanged: (value) => _controller.setDarkMode(value),
          );
        }),
      ],
    );
  }

  Widget _buildLanguageSection() {
    return _buildSettingSection(
      title: 'Язык и локализация',
      icon: Icons.language,
      children: [
        Obx(() {
          return DropdownListTile(
            title: 'Язык приложения',
            subtitle: _getLanguageName(_controller.settings.value.language),
            value: _controller.settings.value.language,
            items: [
              DropdownMenuItem(value: 'ru', child: Text('Русский')),
              DropdownMenuItem(value: 'en', child: Text('English')),
              DropdownMenuItem(value: 'de', child: Text('Deutsch')),
            ],
            onChanged: (value) {
              if (value != null) {
                _controller.setLanguage(value);
              }
            },
          );
        }),
      ],
    );
  }

  Widget _buildNotificationSection() {
    return _buildSettingSection(
      title: 'Уведомления',
      icon: Icons.notifications,
      children: [
        Obx(() {
          return SwitchListTile(
            title: Text('Включить уведомления'),
            subtitle: Text(
              _controller.settings.value.notificationsEnabled
                  ? 'Включены'
                  : 'Выключены',
            ),
            value: _controller.settings.value.notificationsEnabled,
            onChanged: (value) => _controller.setNotifications(value),
          );
        }),
      ],
    );
  }

  Widget _buildFontSizeSection() {
    return _buildSettingSection(
      title: 'Размер шрифта',
      icon: Icons.text_fields,
      children: [
        Obx(() {
          return Padding(
            padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Размер: ${(_controller.settings.value.fontSize * 100).toStringAsFixed(0)}%',
                  style: TextStyle(fontWeight: FontWeight.w600),
                ),
                SizedBox(height: 12),
                Slider(
                  value: _controller.settings.value.fontSize,
                  min: 0.8,
                  max: 1.5,
                  divisions: 7,
                  onChanged: (value) => _controller.setFontSize(value),
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text('80%', style: TextStyle(fontSize: 12, color: Colors.grey)),
                    Text('150%', style: TextStyle(fontSize: 12, color: Colors.grey)),
                  ],
                ),
              ],
            ),
          );
        }),
      ],
    );
  }

  Widget _buildPreviewSection() {
    return _buildSettingSection(
      title: 'Предпросмотр',
      icon: Icons.preview,
      children: [
        Obx(() {
          final fontSize = 14 * _controller.settings.value.fontSize;
          return Padding(
            padding: EdgeInsets.all(16),
            child: Container(
              padding: EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: _controller.settings.value.darkMode
                    ? Colors.grey[900]
                    : Colors.grey[100],
                borderRadius: BorderRadius.circular(8),
              ),
              child: Text(
                'Так выглядит текст в приложении',
                style: TextStyle(
                  fontSize: fontSize,
                  color: _controller.settings.value.darkMode
                      ? Colors.white
                      : Colors.black,
                ),
              ),
            ),
          );
        }),
      ],
    );
  }

  Widget _buildActionSection() {
    return Padding(
      padding: EdgeInsets.symmetric(horizontal: 16, vertical: 24),
      child: Column(
        children: [
          SizedBox(
            width: double.infinity,
            child: ElevatedButton.icon(
              onPressed: _controller.exportSettings,
              icon: Icon(Icons.download),
              label: Text('Экспортировать настройки'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.green,
              ),
            ),
          ),
          SizedBox(height: 12),
          SizedBox(
            width: double.infinity,
            child: ElevatedButton.icon(
              onPressed: () => _showImportDialog(),
              icon: Icon(Icons.upload),
              label: Text('Импортировать настройки'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.blue,
              ),
            ),
          ),
          SizedBox(height: 12),
          SizedBox(
            width: double.infinity,
            child: ElevatedButton.icon(
              onPressed: () => _showResetDialog(),
              icon: Icon(Icons.refresh),
              label: Text('Сбросить на стандартные'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildSettingSection({
    required String title,
    required IconData icon,
    required List<Widget> children,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: EdgeInsets.fromLTRB(16, 24, 16, 8),
          child: Row(
            children: [
              Icon(icon, color: Color(0xFF667EEA), size: 24),
              SizedBox(width: 12),
              Text(
                title,
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.bold,
                  color: Color(0xFF667EEA),
                ),
              ),
            ],
          ),
        ),
        Container(
          margin: EdgeInsets.symmetric(horizontal: 8),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(8),
            border: Border.all(color: Colors.grey[300]!),
          ),
          child: Column(children: children),
        ),
      ],
    );
  }

  void _showResetDialog() {
    Get.dialog(
      AlertDialog(
        title: Text('Сбросить настройки?'),
        content: Text('Все настройки будут восстановлены по умолчанию'),
        actions: [
          TextButton(
            onPressed: () => Get.back(),
            child: Text('Отмена'),
          ),
          TextButton(
            onPressed: () {
              _controller.resetToDefaults();
              Get.back();
            },
            child: Text('Сбросить', style: TextStyle(color: Colors.red)),
          ),
        ],
      ),
    );
  }

  void _showImportDialog() {
    final controller = TextEditingController();
    Get.dialog(
      AlertDialog(
        title: Text('Импортировать настройки'),
        content: TextField(
          controller: controller,
          minLines: 3,
          maxLines: 8,
          decoration: InputDecoration(
            hintText: 'Вставьте JSON настроек',
            border: OutlineInputBorder(),
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Get.back(),
            child: Text('Отмена'),
          ),
          TextButton(
            onPressed: () {
              _controller.importSettings(controller.text);
              Get.back();
            },
            child: Text('Импортировать'),
          ),
        ],
      ),
    );
  }

  String _getLanguageName(String languageCode) {
    switch (languageCode) {
      case 'ru':
        return 'Русский';
      case 'en':
        return 'English';
      case 'de':
        return 'Deutsch';
      default:
        return languageCode;
    }
  }
}

class DropdownListTile extends StatelessWidget {
  final String title;
  final String subtitle;
  final String value;
  final List<DropdownMenuItem<String>> items;
  final ValueChanged<String?> onChanged;

  const DropdownListTile({
    required this.title,
    required this.subtitle,
    required this.value,
    required this.items,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(title),
      subtitle: Text(subtitle),
      trailing: DropdownButton<String>(
        value: value,
        items: items,
        onChanged: onChanged,
        underline: SizedBox(),
      ),
    );
  }
}

Dependencies в pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  get: ^4.6.5
  shared_preferences: ^2.2.0
  flutter_native_splash: ^2.3.0

Применение в main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await SharedPreferences.getInstance();
  final settingsService = SettingsService(prefs);
  
  Get.put(settingsService);

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      home: SettingsPage(),
      themeMode: ThemeMode.system,
    );
  }
}

Ключевые особенности

  • Группировка по категориям — внешний вид, язык, уведомления
  • Переключатели и слайдеры — удобное управление
  • Предпросмотр в реальном времени — сразу видите изменения
  • Экспорт/Импорт — сохранение и восстановление
  • Сброс на стандартные — восстановление умолчаний
  • Персистентность — сохранение в SharedPreferences
  • Валидация — проверка при импорте