Kodokon kodokon.com

Локальное хранение данных с shared_preferences

Сохраняй настройки и лёгкие данные через shared_preferences, зная его ограничения по безопасности и объёму.

8 мин · 3 вопросов

Открыть этот урок в Kodokon

shared_preferences - это хранилище ключ/значение поверх нативных механизмов (SharedPreferences на Android, UserDefaults на iOS). Его зона комфорта: небольшие данные - выбранная тема, пройденный онбординг, последняя активная вкладка. Дальше меняй инструмент: база sqflite или drift для больших или запрашиваемых данных, flutter_secure_storage для всего чувствительного. Файл целиком загружается в память: он не зашифрован и не рассчитан на рост.

BASH
flutter pub add shared_preferences

getInstance() асинхронен при первом вызове (чтение файла), а затем возвращает закешированный экземпляр: последующие вызовы get* синхронны и обслуживаются из памяти. Вызовы set*, наоборот, пишут на диск асинхронно. На практике не разбрасывай строки-ключи по всему приложению: собери их в типизированной, внедряемой и тестируемой обёртке.

DART
import 'package:shared_preferences/shared_preferences.dart';

class SettingsStore {
  SettingsStore(this._prefs);

  static const _darkModeKey = 'settings.darkMode';

  final SharedPreferences _prefs;

  static Future<SettingsStore> load() async {
    final prefs = await SharedPreferences.getInstance();
    return SettingsStore(prefs);
  }

  bool get darkMode =>
      _prefs.getBool(_darkModeKey) ?? false;

  Future<void> setDarkMode(bool value) =>
      _prefs.setBool(_darkModeKey, value);
}
Централизованные ключи, гарантированные типы, внедряемая зависимость.

API хранит только примитивные типы: bool, int, double, String и List<String>. Для структурированного объекта сериализуй его в JSON - твои toJson/fromJson из первого урока снова пригодятся здесь. Принятый компромисс: каждое сохранение перезаписывает значение целиком, и никаких запросов сделать нельзя. Отлично для черновика, не годится для истории.

DART
import 'dart:convert';

import 'package:shared_preferences/shared_preferences.dart';

class Draft {
  const Draft({required this.title, required this.body});

  factory Draft.fromJson(Map<String, dynamic> json) =>
      Draft(
        title: json['title'] as String,
        body: json['body'] as String,
      );

  final String title;
  final String body;

  Map<String, dynamic> toJson() =>
      {'title': title, 'body': body};
}

Future<void> saveDraft(Draft draft) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('draft', jsonEncode(draft));
}

Future<Draft?> loadDraft() async {
  final prefs = await SharedPreferences.getInstance();
  final raw = prefs.getString('draft');
  if (raw == null) return null;
  return Draft.fromJson(
    jsonDecode(raw) as Map<String, dynamic>,
  );
}
Целый объект, сохранённый как простая строка JSON.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Какие данные уместны в shared_preferences?
    • Токен аутентификации пользователя.
    • Полная история пользователя из 50 000 транзакций.
    • Выбор тёмной темы.
  2. Как ведут себя чтения get* после первого getInstance()?
    • Каждое чтение заново читает файл с диска.
    • Они синхронны: значения отдаются из кеша в памяти.
    • Они возвращают Future, который надо ждать каждый раз.
  3. Как сохранить структурированный объект Draft?
    • Сериализовать его в JSON через jsonEncode и сохранить через setString.
    • Использовать prefs.setObject('draft', draft), придуманный для этого.
    • Разложить его по полям в setStringList.