Cara Menggunakan SharedPreferences di Flutter: Panduan Lengkap untuk Developer

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.

Apa Itu SharedPreferences?

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!

Dasar-Dasar Penggunaan SharedPreferences

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.

1. Menginisialisasi SharedPreferences

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 Switch diubah atau TextField diperbarui, data langsung disimpan ke SharedPreferences.
  • setState digunakan untuk memastikan UI diperbarui sesuai dengan preferensi yang baru dimuat atau disimpan.
  • Ada tombol untuk mereset semua preferensi, menunjukkan penggunaan clear().

Kapan Menggunakan SharedPreferences (dan Kapan Tidak)?

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 await saat memanggil SharedPreferences.getInstance() dan menempatkan kode yang menggunakan preferensi setelah itu. Selalu lakukan inisialisasi di dalam fungsi async.

2. Kesalahan Tipe Data (Type Mismatch)

  • Gejala: Data yang dibaca adalah null meskipun Anda yakin sudah menyimpannya, atau terjadi error _CastError.
  • Penyebab: Anda menyimpan data dengan satu tipe (misalnya setInt) tetapi mencoba membacanya dengan tipe lain (misalnya getString), atau sebaliknya.
  • Solusi: Pastikan method set... dan get... yang Anda gunakan konsisten dengan tipe data yang Anda maksud. Misalnya, jika Anda menyimpan angka sebagai setInt('age', 30), bacalah dengan int? 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 di initState atau 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

Apakah SharedPreferences aman untuk menyimpan data sensitif seperti password atau token?

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.

Berapa batasan ukuran data yang bisa disimpan di SharedPreferences?

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.

Apakah SharedPreferences bekerja di semua platform yang didukung Flutter (Android, iOS, Web, Desktop)?

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.

Bagaimana cara memastikan UI saya diperbarui setelah data SharedPreferences berubah?

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


Baca Juga

You May Also Like

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *