Pertemuan 9

Mengobati Aplikasi
Yang Amnesia! 🧠

Pernah main game Android, pas ditutup dan dibuka lagi skornya balik ke 0? Ngajak berantem kan? Yuk belajar cara menyimpan data secara permanen di HP pakai SharedPreferences!

1. RAM vs Storage (Hierarki Memori)

Semua variabel yang kamu buat di Java (misal int skor = 10;) itu disimpannya di Memori RAM. Sifat RAM itu sementara (volatile). Saat aplikasi ditutup paksa, Android membuang isi RAM tersebut sehingga aplikasimu jadi "amnesia".

Kapan Pakai Penyimpanan Apa?

1. Bundle (State)

Gunakan onSaveInstanceState(). Hanya untuk menyelamatkan data UI saat layar HP dirotasi. Hilang kalau aplikasi di-kill.

Fokus Kita!
2. SharedPreferences

Menyimpan data kecil (Teks, Angka, True/False) secara permanen di File Storage HP. Cocok untuk Setting & Auto-Login.

3. Room / SQLite Database

Gunakan ini jika datamu berupa tabel ribuan baris yang saling berelasi (seperti history transaksi toko).

Kaitan Erat dengan Activity Lifecycle

SharedPreferences (baik getSharedPreferences maupun getPreferences) adalah "Penyelamat" dari Lifecycle yang rawan terbunuh (App Process Killed). Pola kerjanya sangat erat dengan status Activity:

  • Fase onCreate() ➔ Waktunya BACA (Load Data)
    Saat layar baru dibentuk, kita langsung membuka loker untuk memuat status terakhir (misal: "Apakah user sudah login?", "Berapa High Score-nya?").
  • Fase onPause() / onStop() ➔ Waktunya TULIS (Auto-Save)
    Sesaat sebelum aplikasi masuk ke background (kehilangan fokus), ini adalah momen kritis untuk menyimpan draf ketikan atau progres game secara otomatis menggunakan editor.apply() agar tidak hilang jika tiba-tiba sistem kehabisan RAM.

Konsep Loker Penitipan (Key-Value)

Sistem SharedPreferences bekerja persis seperti loker penitipan barang. Kamu menitipkan barang (Value) ke dalam loker, lalu diberi nomor kunci (Key). Besok-besok saat mengambil, kamu wajib menyerahkan Kunci yang sama persis!

Mengintip Wujud Asli "DataApp.xml"

Di kode Java, kamu hanya mengetik getSharedPreferences("DataApp", MODE_PRIVATE). Kamu tidak pernah menulis kode untuk "membuat file baru", kan? Lalu file itu datang dari mana?

Kehebatan Android adalah ia secara diam-diam (otomatis) menciptakan file DataApp.xml tersebut saat kamu pertama kali menyimpan data! File XML dipilih karena sangat ringan dan secepat kilat untuk dibaca.

  • Di Simulator Web Ini: Coba lihat panel sebelah kanan (Isi Folder Android). Kotak hitam yang berisi kode <map>...</map> itulah wujud asli dari `DataApp.xml`. Coba ketik namamu dan tekan Masuk, perhatikan bagaimana isinya langsung tertulis!
  • Di HP Android Asli: File ini benar-benar ada secara fisik! Ia disimpan di rute rahasia: /data/data/com.aplikasi.kamu/shared_prefs/DataApp.xml. User biasa tidak akan bisa melihat folder ini lewat Galeri/File Manager, karena sistem Android menguncinya ketat khusus hanya untuk aplikasimu saja!

Pilih Loker Berdasarkan Skalanya:

  • 1. Global (getSharedPreferences)
    Ibarat "Brankas Utama". Data bisa dibaca dari layar mana saja. Digunakan untuk data inti aplikasi.
    Contoh Asli: Token Login/Session, Pengaturan Bahasa Aplikasi, Tema Global.
  • 2. Privat (getPreferences)
    Ibarat "Laci Meja Pribadi". Hanya khusus untuk 1 layar itu saja agar tidak mengotori loker utama.
    Contoh Asli: Pilihan Sortir (Termurah/Terlaris) di halaman Katalog, Status pop-up "Jangan tampilkan tutorial ini lagi" di layar Kamera.

Simulasi Skala Loker

MainActivity
ArtikelActivity

/shared_prefs/DataApp.xml

1 File Dipakai Bersama

getSharedPreferences("DataApp", 0)

Standar Industri (Google Rules)

1. Hanya 5 Tipe Data

Hanya muat untuk tipe primitif: String, int, float, long, dan boolean.

2. Penamaan File

Gunakan format Package Name aplikasimu agar datanya unik. Contoh: com.kampus.app.DATA.

3. Hapus Data

Gunakan editor.remove("KUNCI") menghapus 1 data spesifik, atau editor.clear() membersihkan seluruh isi loker.

2. Simulator Auto-Login

Coba isi nama, centang "Ingat Saya", lalu tekan Masuk. Setelah tiba di Dashboard, coba klik Restart App. Perhatikan bagaimana aplikasi otomatis melompati layar Login karena datamu sudah terselamatkan di SharedPreferences!

KafeApp

Dashboard

Halo,
User!

Selamat datang di Halaman Utama.

Pengaturan Layar

Mode Gelap

State "Mode Gelap" disimpan ke Loker Privat layarnya sendiri menggunakan getPreferences().

Pesan Toast

Simulasi Auto-Login

Tutup paksa & buka lagi aplikasinya.

Isi Folder Android
/shared_prefs/DataApp.xml (Global)
<?xml version="1.0" encoding="utf-8"?>
<map>
<!-- Kosong. Belum ada data yang disimpan -->
</map>

SharedPreferences secara diam-diam membuat file berekstensi .xml tersembunyi di memori internal HP. Kamu bisa melihat simulasi isi file tersebut berubah di kotak atas saat kamu berinteraksi!

3. Bedah Kode: Membangun Auto-Login

Inilah rahasia di balik sistem Auto-Login layaknya aplikasi profesional! Kita akan menggunakan 3 pasang file: Login (MainActivity), Dashboard (HomeActivity), dan Settings (ArtikelActivity).

Cheat Sheet: Baca Data & Nilai Default
Saat kamu membaca data (misal getString(Kunci, Nilai_Bawaan)), kamu WAJIB menyertakan parameter kedua yaitu Default Value. Nilai ini akan dikembalikan oleh sistem jika Kunci tersebut tidak ditemukan di dalam loker (karena user belum pernah login).
Menyimpan (Editor) Membaca (Load)
putInt(key, val) getInt(key, default_val)
putString(key, val) getString(key, default_val)
putBoolean(key, val) getBoolean(key, default_val)

Layar Login (UI)

Membuat form simpel berisi inputan nama, checkbox (Ingat Saya), dan tombol Login.

<!-- activity_main.xml -->
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="32dp"
    android:gravity="center">

    <EditText
        android:id="@+id/etUsername"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Masukkan Username"
        android:layout_marginBottom="16dp" />

    <CheckBox
        android:id="@+id/cbRemember"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Ingat Saya (Auto-Login)"
        android:layout_marginBottom="24dp" />

    <Button
        android:id="@+id/btnLogin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="MASUK" />

</LinearLayout>

4. Ilmu Tingkat Lanjut (Advanced)

Di industri nyata (aplikasi modern 2024+), menggunakan `SharedPreferences` biasa ternyata punya celah keamanan dan keterbatasan. Inilah trik pro untuk mengatasinya:

Kamus Mini: Apa arti MODE_PRIVATE?

Pernah bingung kenapa kita harus selalu mengetik getSharedPreferences("Nama", MODE_PRIVATE)?

MODE_PRIVATE (atau angka 0) adalah parameter Hak Akses (Izin Keamanan). Ini berarti, file XML loker yang kamu buat di HP user HANYA BISA dibaca dan diedit oleh aplikasimu sendiri. Aplikasi lain tidak akan bisa membobol isinya!

Security Alert!

Di tutorial Android jadul, kamu mungkin melihat opsi MODE_WORLD_READABLE. JANGAN PERNAH DIPAKAI! Mode itu membuka gembok file-mu untuk semua aplikasi, bikin sangat gampang diretas hacker! Mode usang tersebut kini sudah dilarang keras oleh sistem Android modern.

A. Mengapa MODE_WORLD_READABLE Diharamkan?

Dulu, developer bisa memaksa loker XML agar bisa dibaca oleh aplikasi lain dengan mengganti parameter MODE_PRIVATE menjadi MODE_WORLD_READABLE.

Ini adalah malapetaka keamanan! Coba pilih mode hak akses di bawah, lalu lihat apa yang terjadi ketika aplikasi Hacker mencoba membaca lokermu!

Terminal Hacker (com.evil.app)
> Menunggu target...
❌ KODE USANG (DEPRECATED API 17): getSharedPreferences("Data", Context.MODE_WORLD_READABLE);
✅ KODE STANDAR INDUSTRI: getSharedPreferences("Data", Context.MODE_PRIVATE);

B. Keamanan Ekstra: EncryptedSharedPreferences

Bahkan jika kamu sudah memakai MODE_PRIVATE, jika HP user sudah di-root, sang hacker masih bisa membuka file XML lokermu dengan akses super-admin dan membaca isinya secara gamblang (plain text)!

Solusinya dari Google: Gunakan library AndroidX Security. Ia akan mengenkripsi nama kunci dan isinya menjadi teks acak (gibberish) yang tidak bisa dibaca manusia.

// 1. Tambahkan di build.gradle: implementation "androidx.security:security-crypto:1.1.0-alpha06"

// 2. Cara pakainya (sedikit lebih panjang dari biasa):
MasterKey masterKey = new MasterKey.Builder(this)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build();

SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
    this,
    "rahasia_app_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

// 3. Simpan data seperti biasa! Hasil di XML-nya akan berbentuk acak spt: "XyZ1@#kK="
sharedPreferences.edit().putString("TOKEN", "Rahasia123").apply();

C. Reactive: Memantau Perubahan Data

Bagaimana jika kamu punya Layar A (Home) dan Layar B (Setting)? Jika user mengubah Mode Gelap di Layar B, bagaimana Layar A bisa tahu dan ikut berubah secara otomatis (real-time)?
Jawabannya: Pasang "telinga pendengar" bernama OnSharedPreferenceChangeListener.

// Di dalam MainActivity (Layar A), pasang pendengar:
SharedPreferences.OnSharedPreferenceChangeListener listener = 
    new SharedPreferences.OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
            if (key.equals("is_dark_mode")) {
                // Data berubah! Segera update UI layar ini secara otomatis!
                boolean isDark = prefs.getBoolean(key, false);
                // ... panggil fungsi ganti warna ...
            }
        }
};

// Daftarkan telinga tersebut ke lokernya
pref.registerOnSharedPreferenceChangeListener(listener);

D. Trik Pro GSON: Gimana Kalau Mau Nyimpen Object?

Aturan resminya kan nggak boleh nyimpen Object Java utuh (misal class User yang berisi Nim, Nama, Umur). Tapi di dunia kerja, developer mengakalinya menggunakan Library GSON buatan Google! Coba simulator di bawah ini:

1. Object Java (User.java)

2. Hasil di SharedPreferences XML

<map>
    <!-- Disimpan sebagai String panjang! -->
    <string name="DATA_USER_LENGKAP">
        {"nama":"Budi Santoso","umur":20}
    </string>
</map>

Implementasi Kodenya:

// 1. Tambahkan di build.gradle: implementation 'com.google.code.gson:gson:2.10.1'

// 2. Menulis Data (Save)
User userBaru = new User("Budi Santoso", 20);

// Blender object jadi string format JSON
Gson gson = new Gson();
String jsonTeks = gson.toJson(userBaru); 

editor.putString("DATA_USER_LENGKAP", jsonTeks);
editor.apply();

// 3. Membaca Data (Load) & Mengembalikannya jadi Object
String jsonMasuk = pref.getString("DATA_USER_LENGKAP", "");

if (!jsonMasuk.isEmpty()) {
    // Sulap string JSON kembali menjadi Object User.java
    User userTersimpan = gson.fromJson(jsonMasuk, User.class);
    
    // Sekarang bisa pakai method Getter-nya lagi!
    String namanya = userTersimpan.getNama();
}

5. Tugas Implementasi (Challenge)

Udah paham konsep penitipan tasnya? Coba aplikasikan ilmu ini ke kasus lain yang sangat populer: Sistem High Score Game!

Misi Aplikasi "Game Clicker":

  1. Buat project baru. Tampilan cukup berisi 2 teks (Teks "Skor Sekarang" & Teks "High Score") dan 1 Tombol "KLIK SAYA!".
  2. Tiap kali tombol diklik, variabel skor (integer) bertambah 1 dan mengubah tulisan "Skor Sekarang".
  3. Syarat SharedPreferences: Setiap kali tombol diklik, cek apakah "Skor Sekarang" lebih besar dari "High Score" yang tersimpan? Jika iya, selamatkan skor baru tersebut ke SharedPreferences pakai editor.putInt(...)!
  4. Pastikan di dalam `onCreate()`, kamu menarik data High Score dari SharedPreferences untuk langsung ditampilkan ke layar saat aplikasi dibuka pertama kali. Gunakan angka 0 sebagai Nilai Default.
  5. Tantangan Ekstra: Tambahkan satu Tombol "RESET REKOR". Jika ditekan, hapus data High Score di SharedPreferences menggunakan editor.remove(), lalu kembalikan angka High Score di layar menjadi 0.
  6. Tutup paksa aplikasi (swipe up dari Recent Apps), buka lagi. Jika High Score tidak balik ke 0, KAMU BERHASIL! 🏆