Kodokon kodokon.com

Persistencia local con shared_preferences

Persiste preferencias y datos ligeros con shared_preferences conociendo sus límites de seguridad y de volumen.

8 min · 3 preguntas

Abrir esta lección en Kodokon

shared_preferences es un almacén clave/valor respaldado por mecanismos nativos (SharedPreferences en Android, UserDefaults en iOS). Su punto fuerte: datos pequeños (tema elegido, onboarding visto, última pestaña activa). Más allá de eso, cambia de herramienta: una base de datos sqflite o drift para datos grandes o consultables, flutter_secure_storage para todo lo sensible. El archivo se carga por completo en memoria: no está cifrado ni pensado para crecer.

BASH
flutter pub add shared_preferences

getInstance() es asíncrono en la primera llamada (lee el archivo) y luego devuelve una instancia en caché: las llamadas get* posteriores son síncronas, servidas desde la memoria. Las llamadas set*, en cambio, escriben en disco de forma asíncrona. En la práctica, no disperses las cadenas de las claves por toda la aplicación: centralízalas en un envoltorio tipado, inyectable y testeable.

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);
}
Claves centralizadas, tipos garantizados, dependencia inyectable.

La API solo almacena tipos primitivos: bool, int, double, String y List<String>. Para un objeto estructurado, serialízalo a JSON: tus toJson/fromJson de la primera lección vuelven a ser útiles aquí. Un compromiso aceptado: cada guardado reescribe el valor completo y no es posible ninguna consulta. Perfecto para un borrador, inadecuado para un historial.

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>,
  );
}
Un objeto completo almacenado como una simple cadena JSON.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué datos corresponden a shared_preferences?
    • El token de autenticación del usuario.
    • El historial completo de 50.000 transacciones del usuario.
    • La elección del tema oscuro.
  2. Tras el primer getInstance(), ¿cómo se comportan las lecturas get*?
    • Cada lectura vuelve a leer el archivo desde el disco.
    • Son síncronas: los valores se sirven desde una caché en memoria.
    • Devuelven Future que debes esperar con await cada vez.
  3. ¿Cómo guardas un objeto Draft estructurado?
    • Serialízalo a JSON con jsonEncode y almacénalo con setString.
    • Usa prefs.setObject('draft', draft), diseñado para este propósito.
    • Divídelo campo por campo en un setStringList.