SharedPreferences #

SharedPreferences adalah solusi penyimpanan key-value paling sederhana di Flutter. Ia menyimpan data dalam format yang ringan dan persisten — data tetap ada meskipun aplikasi ditutup dan dibuka kembali. Ia bukan database, bukan untuk data kompleks, tapi untuk satu tugas ia sangat baik: menyimpan preferensi dan pengaturan pengguna.

Instalasi #

# pubspec.yaml
dependencies:
  shared_preferences: ^2.3.4

Operasi Dasar #

SharedPreferences hanya mendukung tipe primitif: int, double, bool, String, dan List<String>.

import 'package:shared_preferences/shared_preferences.dart';

// Mendapatkan instance -- async, panggil sekali saja
final prefs = await SharedPreferences.getInstance();

// TULIS
await prefs.setString('nama_pengguna', 'Budi');
await prefs.setInt('usia', 25);
await prefs.setDouble('saldo', 150000.50);
await prefs.setBool('isDarkMode', true);
await prefs.setStringList('bahasa_favorit', ['id', 'en', 'jp']);

// BACA (sinkron -- tidak perlu await setelah getInstance)
final nama = prefs.getString('nama_pengguna');       // 'Budi' atau null
final usia = prefs.getInt('usia');                   // 25 atau null
final saldo = prefs.getDouble('saldo');              // 150000.50 atau null
final isDarkMode = prefs.getBool('isDarkMode');      // true atau null
final bahasa = prefs.getStringList('bahasa_favorit'); // ['id','en','jp'] atau null

// Dengan nilai default jika key tidak ada
final tema = prefs.getString('tema') ?? 'sistem';
final notifAktif = prefs.getBool('notif') ?? true;

// HAPUS
await prefs.remove('nama_pengguna');  // hapus satu key
await prefs.clear();                  // hapus semua data!

// CEK
final adaKey = prefs.containsKey('nama_pengguna');  // true / false

// LIHAT SEMUA KEY
final semuaKey = prefs.getKeys();  // Set<String>

SharedPreferencesAsync — API Modern #

Sejak versi 2.3.0, tersedia SharedPreferencesAsync yang fully async dan lebih konsisten:

import 'package:shared_preferences/shared_preferences.dart';

final prefs = SharedPreferencesAsync();

// Semua operasi async
await prefs.setString('tema', 'gelap');
final tema = await prefs.getString('tema');   // perlu await

// Atau gunakan SharedPreferencesWithCache untuk performa lebih baik
// (data di-cache di memory setelah pertama kali dibaca)
final cachedPrefs = await SharedPreferencesWithCache.create(
  cacheOptions: const SharedPreferencesWithCacheOptions(
    // Tentukan key mana yang perlu di-cache
    allowList: {'tema', 'bahasa', 'isDarkMode'},
  ),
);

// Setelah cache diisi, baca bisa sinkron
final tema2 = cachedPrefs.getString('tema');  // tidak perlu await!

Wrapper Type-Safe — Hindari Magic String #

Menggunakan string key secara langsung di seluruh kode adalah anti-pattern. Buat wrapper yang memusatkan semua key dan operasi:

// core/storage/app_preferences.dart
class AppPreferences {
  static const _kTema = 'tema';
  static const _kBahasa = 'bahasa';
  static const _kIsDarkMode = 'is_dark_mode';
  static const _kHasSeenOnboarding = 'has_seen_onboarding';
  static const _kFcmToken = 'fcm_token';
  static const _kLastSyncAt = 'last_sync_at';

  final SharedPreferences _prefs;
  AppPreferences(this._prefs);

  // Factory untuk kemudahan pembuatan
  static Future<AppPreferences> create() async {
    final prefs = await SharedPreferences.getInstance();
    return AppPreferences(prefs);
  }

  // Tema
  String get tema => _prefs.getString(_kTema) ?? 'sistem';
  Future<void> setTema(String tema) => _prefs.setString(_kTema, tema);

  // Bahasa
  String get bahasa => _prefs.getString(_kBahasa) ?? 'id';
  Future<void> setBahasa(String bahasa) => _prefs.setString(_kBahasa, bahasa);

  // Dark mode
  bool get isDarkMode => _prefs.getBool(_kIsDarkMode) ?? false;
  Future<void> setIsDarkMode(bool value) => _prefs.setBool(_kIsDarkMode, value);

  // Onboarding
  bool get hasSeenOnboarding => _prefs.getBool(_kHasSeenOnboarding) ?? false;
  Future<void> markOnboardingSeen() =>
      _prefs.setBool(_kHasSeenOnboarding, true);

  // FCM Token
  String? get fcmToken => _prefs.getString(_kFcmToken);
  Future<void> setFcmToken(String token) =>
      _prefs.setString(_kFcmToken, token);

  // Last sync timestamp
  DateTime? get lastSyncAt {
    final ms = _prefs.getInt(_kLastSyncAt);
    return ms != null ? DateTime.fromMillisecondsSinceEpoch(ms) : null;
  }

  Future<void> setLastSyncAt(DateTime time) =>
      _prefs.setInt(_kLastSyncAt, time.millisecondsSinceEpoch);

  // Reset semua preferensi (logout)
  Future<void> clearAll() => _prefs.clear();
}

Integrasi dengan Riverpod #

// Sediakan AppPreferences via Provider
final appPreferencesProvider = Provider<AppPreferences>((ref) {
  throw UnimplementedError('Initialize in main()');
});

// main.dart -- inisialisasi sebelum runApp
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await AppPreferences.create();

  runApp(
    ProviderScope(
      overrides: [
        appPreferencesProvider.overrideWithValue(prefs),
      ],
      child: const MyApp(),
    ),
  );
}

// Notifier untuk tema -- reactive state dari SharedPreferences
class TemaNotifier extends Notifier<ThemeMode> {
  @override
  ThemeMode build() {
    final prefs = ref.read(appPreferencesProvider);
    // Baca nilai awal dari SharedPreferences
    return prefs.isDarkMode ? ThemeMode.dark : ThemeMode.light;
  }

  Future<void> toggle() async {
    final prefs = ref.read(appPreferencesProvider);
    final newValue = state == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
    await prefs.setIsDarkMode(newValue == ThemeMode.dark);
    state = newValue;
  }
}

final temaProvider = NotifierProvider<TemaNotifier, ThemeMode>(
  TemaNotifier.new,
);

// Di widget
class TemaToggle extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final themeMode = ref.watch(temaProvider);
    return Switch(
      value: themeMode == ThemeMode.dark,
      onChanged: (_) => ref.read(temaProvider.notifier).toggle(),
    );
  }
}

// Di MaterialApp
class MyApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final themeMode = ref.watch(temaProvider);
    return MaterialApp(
      themeMode: themeMode,
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      home: const HomeScreen(),
    );
  }
}

Menyimpan Objek Kompleks #

SharedPreferences tidak mendukung objek langsung — simpan sebagai JSON string:

// Simpan objek sebagai JSON
Future<void> simpanPengaturanNotif(PengaturanNotif pengaturan) async {
  final prefs = await SharedPreferences.getInstance();
  final json = jsonEncode(pengaturan.toJson());
  await prefs.setString('pengaturan_notif', json);
}

// Baca kembali
Future<PengaturanNotif?> getPengaturanNotif() async {
  final prefs = await SharedPreferences.getInstance();
  final json = prefs.getString('pengaturan_notif');
  if (json == null) return null;
  return PengaturanNotif.fromJson(jsonDecode(json));
}
Peringatan: Jika objek yang kamu simpan sebagai JSON mulai kompleks (nested objects, list panjang), ini sinyal bahwa kamu sudah melampaui batas SharedPreferences. Pertimbangkan beralih ke Hive atau ObjectBox.

Testing dengan SharedPreferences #

// Gunakan setMockInitialValues untuk test
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  group('AppPreferences', () {
    setUp(() {
      // Set nilai awal untuk test -- tidak menyentuh SharedPreferences asli
      SharedPreferences.setMockInitialValues({
        'is_dark_mode': false,
        'bahasa': 'id',
      });
    });

    test('isDarkMode default ke false', () async {
      final prefs = await AppPreferences.create();
      expect(prefs.isDarkMode, isFalse);
    });

    test('setIsDarkMode menyimpan nilai', () async {
      final prefs = await AppPreferences.create();
      await prefs.setIsDarkMode(true);
      expect(prefs.isDarkMode, isTrue);
    });
  });
}

Kapan TIDAK Menggunakan SharedPreferences #

✗ Data sensitif -- gunakan flutter_secure_storage
✗ List panjang (>100 item) -- gunakan Hive atau Drift
✗ Data dengan relasi antar objek -- gunakan ObjectBox atau Drift
✗ Data yang butuh query/filter -- tidak ada query di SharedPreferences
✗ Data besar (>beberapa KB) -- SharedPreferences tidak didesain untuk ini
✗ Operasi batch yang sering -- performa sangat lambat untuk banyak operasi

✓ IDEAL untuk:
  Tema (gelap/terang), bahasa, ukuran font
  Status onboarding (sudah lihat atau belum)
  Flag fitur (notifikasi aktif/nonaktif)
  Timestamp terakhir sync
  FCM registration token

Ringkasan #

  • SharedPreferences menyimpan data key-value secara persisten — data tetap ada setelah app ditutup dan dibuka kembali.
  • Hanya mendukung tipe primitif: int, double, bool, String, dan List<String>. Untuk objek kompleks, serialize ke JSON String — tapi ini sinyal untuk beralih ke solusi lain jika terlalu kompleks.
  • SharedPreferencesAsync dan SharedPreferencesWithCache adalah API modern yang lebih konsisten dan lebih cepat — pertimbangkan untuk proyek baru.
  • Selalu buat wrapper type-safe yang memusatkan semua key sebagai konstanta — hindari magic string yang tersebar di seluruh kodebase.
  • Inisialisasi di main() sebelum runApp(), lalu sediakan via Provider/Riverpod untuk akses di seluruh app tanpa perlu getInstance() berulang kali.
  • Gunakan SharedPreferences.setMockInitialValues() untuk unit test tanpa menyentuh storage asli.
  • Jangan gunakan untuk data sensitif (token/password), list panjang, data dengan relasi, atau data yang butuh query.

← Sebelumnya: Overview   Berikutnya: Hive →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact