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, danList<String>. Untuk objek kompleks, serialize ke JSON String — tapi ini sinyal untuk beralih ke solusi lain jika terlalu kompleks.SharedPreferencesAsyncdanSharedPreferencesWithCacheadalah 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()sebelumrunApp(), lalu sediakan via Provider/Riverpod untuk akses di seluruh app tanpa perlugetInstance()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.