Dalam pengembangan aplikasi Flutter, seringkali kita perlu menyimpan data kecil secara lokal agar aplikasi bisa “mengingat” preferensi pengguna atau status tertentu bahkan setelah ditutup dan dibuka kembali. Bayangkan aplikasi Anda yang tiba-tiba melupakan mode gelap yang sudah Anda aktifkan, atau harus login ulang setiap kali dibuka. Tentu tidak praktis, bukan?
Di sinilah peran SharedPreferences menjadi krusial. Ini adalah salah satu cara paling sederhana dan umum untuk menyimpan data primitif (seperti String, int, bool, double, dan List<String>) secara persisten di perangkat pengguna. Sebagai developer, memahami cara kerja SharedPreferences bukan hanya tentang menyimpan data, tetapi juga tentang memberikan pengalaman pengguna yang lebih mulus dan personal.
SharedPreferences adalah mekanisme penyimpanan data key-value pair yang ringan dan tersedia di ekosistem Flutter. Secara under the hood, di Android, ia menggunakan Android SharedPreferences, dan di iOS menggunakan UserDefaults. Plugin Flutter untuk SharedPreferences menyediakan API yang seragam untuk berinteraksi dengan mekanisme penyimpanan lokal tersebut di berbagai platform.
Ini sangat cocok untuk data yang tidak terlalu kompleks atau sensitif, seperti:
- Preferensi pengguna (mode gelap, bahasa, notifikasi)
- Status onboarding aplikasi
- Menyimpan token akses yang tidak sensitif (atau yang dienkripsi secara terpisah)
- Data kecil lainnya yang perlu dipertahankan antar sesi aplikasi
Persiapan: Menambahkan Dependensi
Sebelum kita bisa mulai menggunakan SharedPreferences, kita perlu menambahkan paket shared_preferences ke proyek Flutter Anda. Buka file pubspec.yaml di root proyek Anda dan tambahkan baris berikut di bawah bagian dependencies:
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.2.2 # Gunakan versi terbaru yang tersedia
Setelah menambahkan dependensi, simpan file pubspec.yaml. Editor Anda (seperti VS Code atau Android Studio) biasanya akan otomatis menjalankan flutter pub get. Jika tidak, jalankan perintah ini secara manual di terminal proyek Anda:
flutter pub get
Sekarang, Anda sudah siap untuk mengimpor dan menggunakan SharedPreferences di kode Dart Anda!
Semua operasi SharedPreferences bersifat asinkron (mengembalikan Future), jadi Anda akan sering menggunakan async dan await saat berinteraksi dengannya. Ini penting untuk memastikan operasi I/O selesai sebelum Anda mencoba menggunakan data.
Langkah pertama adalah mendapatkan instance dari SharedPreferences. Ini adalah operasi asinkron karena mungkin perlu waktu untuk memuat preferensi dari disk. Biasanya, Anda akan memanggilnya di awal method atau kelas yang membutuhkan akses ke preferensi.
import 'package:shared_preferences/shared_preferences.dart';
Future<SharedPreferences> _getPrefsInstance() async {
return await SharedPreferences.getInstance();
}
Penting untuk selalu menunggu (await) instance ini sebelum melakukan operasi lainnya.
2. Menyimpan Data
SharedPreferences mendukung penyimpanan beberapa tipe data primitif. Setiap data disimpan dengan sebuah ‘key’ (kunci) bertipe String yang unik. Key ini akan digunakan untuk mengambil data tersebut di kemudian hari.
- String:
setString(key, value) - Integer:
setInt(key, value) - Boolean:
setBool(key, value) - Double:
setDouble(key, value) - List<String>:
setStringList(key, value)
Berikut contoh cara menyimpannya:
Future<void> saveData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
// Menyimpan String
await prefs.setString('username', 'tubianto_dev');
// Menyimpan Integer
await prefs.setInt('user_id', 12345);
// Menyimpan Boolean
await prefs.setBool('is_dark_mode', true);
// Menyimpan Double
await prefs.setDouble('app_version', 1.0);
// Menyimpan List<String>
await prefs.setStringList('favorite_techs', ['Flutter', 'Python', 'AI']);
print('Data berhasil disimpan!');
}
Setiap method set... akan mengembalikan Future<bool> yang mengindikasikan apakah operasi penyimpanan berhasil atau tidak. Meskipun jarang perlu dicek secara langsung, ini bisa berguna untuk penanganan error yang lebih canggih.
3. Membaca Data
Untuk membaca data, Anda menggunakan method get... yang sesuai dengan tipe data yang Anda simpan. Jika key yang Anda minta tidak ditemukan, method ini akan mengembalikan null.
Future<void> readData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
// Membaca String
String? username = prefs.getString('username');
print('Username: ${username ?? 'Tidak ada username'}'); // Menggunakan null-aware operator
// Membaca Integer
int? userId = prefs.getInt('user_id');
print('User ID: ${userId ?? 0}'); // Memberikan nilai default 0 jika null
// Membaca Boolean
bool? isDarkMode = prefs.getBool('is_dark_mode');
print('Is Dark Mode: ${isDarkMode ?? false}'); // Memberikan nilai default false jika null
// Membaca Double
double? appVersion = prefs.getDouble('app_version');
print('App Version: ${appVersion ?? 0.0}');
// Membaca List<String>
List<String>? favoriteTechs = prefs.getStringList('favorite_techs');
print('Favorite Techs: ${favoriteTechs ?? []}');
}
Penting untuk selalu menangani kasus di mana data mungkin null, terutama saat pertama kali aplikasi dijalankan atau setelah data dihapus. Penggunaan ?? (null-aware operator) adalah cara yang elegan untuk memberikan nilai default.
4. Menghapus Data
Anda bisa menghapus data individual berdasarkan key-nya, atau menghapus semua data yang disimpan oleh aplikasi.
- Menghapus satu item:
remove(key) - Menghapus semua item:
clear()
Future<void> deleteData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
// Menghapus data dengan key 'username'
await prefs.remove('username');
print('Key "username" berhasil dihapus.');
// Menghapus semua data yang disimpan aplikasi
// Hati-hati menggunakan ini, karena akan menghapus SEMUA preferensi aplikasi!
// await prefs.clear();
// print('Semua data SharedPreferences berhasil dihapus.');
}
Studi Kasus: Membuat Aplikasi Pengaturan Sederhana
Mari kita buat contoh sederhana yang menunjukkan bagaimana SharedPreferences dapat digunakan dalam sebuah aplikasi untuk menyimpan preferensi mode gelap dan nama pengguna.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isDarkMode = false;
String _userName = 'Guest';
TextEditingController _nameController = TextEditingController();
@override
void initState() {
super.initState();
_loadPreferences();
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
Future<void> _loadPreferences() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_isDarkMode = prefs.getBool('is_dark_mode') ?? false;
_userName = prefs.getString('user_name') ?? 'Guest';
_nameController.text = _userName;
});
}
Future<void> _toggleDarkMode(bool value) async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_isDarkMode = value;
});
await prefs.setBool('is_dark_mode', value);
}
Future<void> _saveUserName(String name) async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_userName = name;
});
await prefs.setString('user_name', name);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SharedPreferences Demo',
theme: _isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: Scaffold(
appBar: AppBar(
title: const Text('Pengaturan Aplikasi'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Halo, $_userName!',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Mode Gelap'),
Switch(
value: _isDarkMode,
onChanged: _toggleDarkMode,
),
],
),
const SizedBox(height: 20),
TextField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Nama Pengguna',
border: OutlineInputBorder(),
),
onChanged: _saveUserName,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
_loadPreferences(); // Muat ulang untuk mengosongkan semua
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Semua preferensi dihapus!')),
);
},
child: const Text('Reset Semua Preferensi'),
),
],
),
),
),
);
}
}
Dalam contoh ini:
- Preferensi dimuat saat aplikasi pertama kali diinisialisasi (di
initState). - Saat
Switchdiubah atauTextFielddiperbarui, data langsung disimpan ke SharedPreferences. setStatedigunakan untuk memastikan UI diperbarui sesuai dengan preferensi yang baru dimuat atau disimpan.- Ada tombol untuk mereset semua preferensi, menunjukkan penggunaan
clear().
Memilih alat penyimpanan data yang tepat adalah bagian penting dari arsitektur aplikasi. SharedPreferences, meskipun sangat berguna, memiliki batasan:
Cocok untuk:
- Preferensi Pengguna: Mode gelap, ukuran font, bahasa, pengaturan notifikasi.
- Status Aplikasi: Apakah pengguna sudah melihat tutorial onboarding, status login sederhana (jika tidak ada data sensitif).
- Data Kecil Non-Sensitif: Misalnya, ID item terakhir yang dilihat, riwayat pencarian singkat.
Tidak Cocok untuk:
- Data Sensitif: Password, PIN, token autentikasi kritis, informasi finansial. SharedPreferences tidak menyediakan enkripsi bawaan, sehingga data dapat diakses oleh pihak yang berwenang di perangkat. Untuk ini, gunakan paket seperti
flutter_secure_storage. - Data Terstruktur Kompleks: Objek yang memiliki banyak properti atau hubungan antar objek. SharedPreferences hanya cocok untuk data primitif atau List<String>. Untuk data semacam ini, pertimbangkan database lokal seperti SQLite (dengan paket
sqflite) atau penyimpanan NoSQL seperti Hive atau Realm. - Data Dalam Jumlah Besar: Meskipun bisa menyimpan cukup banyak, SharedPreferences tidak dioptimalkan untuk performa saat membaca/menulis data dalam jumlah besar atau sering. Ini bisa memperlambat waktu startup aplikasi atau menyebabkan jank UI.
Masalah yang Sering Terjadi
Sebagai developer, Anda mungkin akan menemui beberapa masalah umum saat menggunakan SharedPreferences. Berikut beberapa di antaranya:
1. Future Belum Di-await saat getInstance()
- Gejala: Aplikasi crash dengan error
Bad state: No instance of SharedPreferences. Did you call SharedPreferences.getInstance() yet?atau perilaku tidak terduga saat membaca/menulis data. - Penyebab: Anda mencoba mengakses instance SharedPreferences sebelum operasi asinkron
SharedPreferences.getInstance()selesai. - Solusi: Pastikan Anda selalu menggunakan
awaitsaat memanggilSharedPreferences.getInstance()dan menempatkan kode yang menggunakan preferensi setelah itu. Selalu lakukan inisialisasi di dalam fungsiasync.
2. Kesalahan Tipe Data (Type Mismatch)
- Gejala: Data yang dibaca adalah
nullmeskipun Anda yakin sudah menyimpannya, atau terjadi error_CastError. - Penyebab: Anda menyimpan data dengan satu tipe (misalnya
setInt) tetapi mencoba membacanya dengan tipe lain (misalnyagetString), atau sebaliknya. - Solusi: Pastikan method
set...danget...yang Anda gunakan konsisten dengan tipe data yang Anda maksud. Misalnya, jika Anda menyimpan angka sebagaisetInt('age', 30), bacalah denganint? age = prefs.getInt('age').
3. Key yang Tidak Konsisten Antar Operasi
- Gejala: Data tidak ditemukan atau tidak diperbarui, bahkan setelah disimpan.
- Penyebab: Anda menggunakan key yang berbeda saat menyimpan dan membaca data (misalnya, menyimpan dengan ‘user_name’ tetapi membaca dengan ‘username’). Typos adalah penyebab umum.
- Solusi: Gunakan konstanta atau enum untuk menyimpan key SharedPreferences Anda untuk menghindari kesalahan ketik dan memastikan konsistensi di seluruh aplikasi.
4. UI Tidak Update Secara Otomatis
- Gejala: Data berhasil disimpan atau dimuat, tetapi tampilan UI tidak berubah mencerminkan data terbaru.
- Penyebab: Anda lupa memanggil
setState()setelah mengubah variabel yang terikat pada UI, atau preferensi dimuat di luar lifecycle widget yang benar. - Solusi: Pastikan perubahan pada state yang memengaruhi UI dilakukan di dalam blok
setState(() { ... });. Untuk loading awal, pastikan data dimuat diinitStateatau dipantau oleh state management solution.
Pengalaman dan Pertimbangan Praktis
Setelah sekian lama berkecimpung dengan pengembangan aplikasi, ada beberapa hal praktis terkait SharedPreferences yang seringkali menjadi sorotan:
1. Sifat Asinkron (async/await) Itu Penting
Jangan pernah melupakan bahwa semua operasi SharedPreferences mengembalikan Future. Ini berarti mereka tidak langsung selesai. Banyak developer pemula sering lupa menggunakan await, yang mengakibatkan aplikasi mencoba mengakses data yang belum dimuat atau menulis data yang belum selesai, berujung pada error atau perilaku yang tidak konsisten. Selalu pikirkan alur asinkron saat bekerja dengan I/O.
2. Keamanan Data Adalah Prioritas (Jangan Simpan yang Sensitif!)
Ini adalah poin yang tidak bisa ditekankan cukup. SharedPreferences tidak terenkripsi. Ini artinya, jika perangkat pengguna di-root atau di-jailbreak, atau jika ada aplikasi jahat yang memiliki izin yang tepat, data di SharedPreferences bisa dibaca. Dalam praktiknya, ini berarti jangan pernah menyimpan password, token autentikasi penting, kunci API, atau informasi pribadi sensitif lainnya di sini. Untuk data semacam itu, flutter_secure_storage atau layanan backend yang aman adalah pilihan yang jauh lebih baik.
3. Pertimbangkan Penggunaan State Management
Untuk aplikasi yang lebih kompleks, mengelola preferensi langsung di dalam setiap StatefulWidget bisa jadi rumit. Menggabungkan SharedPreferences dengan solusi state management seperti Provider, Riverpod, atau BLoC bisa sangat meningkatkan kebersihan kode dan kemudahan pemeliharaan. Misalnya, Anda bisa membuat sebuah SettingsProvider yang bertanggung jawab untuk memuat dan menyimpan preferensi, lalu mengexpose preferensi tersebut ke seluruh widget yang membutuhkannya. Ini juga memudahkan pengujian!
4. Pengujian Otomatis (Unit Testing)
Salah satu keunggulan SharedPreferences di Flutter adalah relatif mudah untuk diuji. Paket shared_preferences menyediakan sebuah fungsi bernama SharedPreferences.setMockInitialValues() yang memungkinkan Anda untuk memberikan nilai awal ke SharedPreferences untuk tujuan pengujian. Ini sangat berguna untuk memastikan logika bisnis Anda bekerja dengan benar berdasarkan preferensi yang berbeda tanpa perlu interaksi UI atau perangkat sungguhan.
5. Batasi Penggunaan clear()
Method clear() akan menghapus semua data yang disimpan oleh aplikasi Anda di SharedPreferences. Gunakan dengan sangat hati-hati dan hanya ketika memang ada kebutuhan yang jelas, seperti saat pengguna melakukan logout total atau ingin mereset aplikasi ke pengaturan default pabrik. Seringkali, menghapus item spesifik dengan remove(key) lebih aman dan tepat guna.
Dengan mempertimbangkan poin-poin ini, Anda tidak hanya akan bisa menggunakan SharedPreferences dengan efektif, tetapi juga membangun aplikasi yang lebih tangguh, aman, dan mudah dikelola.
FAQ
Tidak. SharedPreferences tidak menyediakan enkripsi bawaan, sehingga data yang disimpan dapat diakses oleh pihak yang berwenang di perangkat. Untuk data sensitif, gunakan solusi penyimpanan yang aman seperti flutter_secure_storage atau solusi backend yang terenkripsi.
Tidak ada batasan ukuran data yang keras. Namun, SharedPreferences dirancang untuk data kecil. Menyimpan data dalam jumlah sangat besar (misalnya, ratusan KB atau MB) tidak disarankan karena dapat mempengaruhi performa aplikasi, terutama saat startup.
Ya. Plugin shared_preferences dirancang untuk bekerja secara konsisten di Android, iOS, Web, dan platform Desktop (Windows, macOS, Linux) yang didukung Flutter, meskipun implementasi under the hood bervariasi per platform.
Apa perbedaan antara remove(key) dan clear()?
remove(key) menghapus data spesifik yang terkait dengan key yang diberikan. Sedangkan clear() menghapus semua data yang disimpan oleh aplikasi di SharedPreferences. Hati-hati saat menggunakan clear() karena ia akan menghapus semua preferensi aplikasi.
Setelah menyimpan atau memuat data dari SharedPreferences, Anda perlu memanggil setState() di dalam StatefulWidget Anda untuk memicu rebuild UI dan menampilkan perubahan data. Jika menggunakan state management, pastikan provider/bloc/dll. Anda memberitahu listener untuk rebuild.
Kesimpulan
SharedPreferences adalah fondasi yang sangat penting bagi setiap developer Flutter yang ingin membangun aplikasi yang interaktif dan personal. Ia menawarkan cara yang mudah dan efisien untuk menyimpan preferensi pengguna dan data kecil lainnya secara lokal. Dengan memahami cara kerjanya, kapan harus menggunakannya, dan batasan-batasannya, Anda dapat menciptakan pengalaman pengguna yang lebih baik dan menjaga aplikasi Anda tetap responsif.
Ingatlah untuk selalu memprioritaskan keamanan data dan memilih solusi penyimpanan yang tepat sesuai dengan kebutuhan dan sensitivitas data Anda. Penggunaan SharedPreferences yang bijak akan menjadi aset berharga dalam toolkit pengembangan Flutter Anda.
TAGS: Flutter, SharedPreferences, Data Persistence, Local Storage, Mobile Development, Android, iOS, Dart, App Preferences


