Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

Android SharedPreferences: คู่มือครบถ้วนพร้อมตัวอย่าง


SharedPreferences คืออะไร?

SharedPreferences เป็นกลไกเก็บข้อมูล Key-Value แบบง่าย บน Internal Storage ของแอป ใช้สำหรับข้อมูลเล็กน้อย เช่น Settings, Preferences, Token, Flag, User Profile สำคัญคือ Private (แอปอื่นเข้าถึงไม่ได้)

Mode การเข้าถึง

Modeคำอธิบาย
MODE_PRIVATE (0)เฉพาะแอปตัวนี้เข้าถึงได้ (Default, แนะนำ)
MODE_WORLD_READABLEDeprecated (API 17+) - ไม่ปลอดภัย
MODE_WORLD_WRITEABLEDeprecated (API 17+) - ไม่ปลอดภัย
MODE_MULTI_PROCESSDeprecated (API 23+) - ไม่ทำงานเชื่อถือได้

API หลัก (SharedPreferences & Editor)

ดึง SharedPreferences Instance

// Default SharedPreferences (ต่อ Package Name)
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);

// Named SharedPreferences (แยกไฟล์)
SharedPreferences prefs = context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);

// ใน Activity
SharedPreferences prefs = getSharedPreferences("user_prefs", MODE_PRIVATE);
SharedPreferences prefs = getPreferences(MODE_PRIVATE); // ใช้ชื่อ Activity

อ่านข้อมูล (SharedPreferences)

String name = prefs.getString("key_name", "Default Name");
int age = prefs.getInt("key_age", 0);
boolean isLoggedIn = prefs.getBoolean("key_logged_in", false);
float rating = prefs.getFloat("key_rating", 0f);
long timestamp = prefs.getLong("key_time", 0L);
Set stringSet = prefs.getStringSet("key_set", new HashSet<>());

// Check exists
boolean hasKey = prefs.contains("key_name");

// Get All (Map)
Map all = prefs.getAll();

เขียนข้อมูล (SharedPreferences.Editor)

SharedPreferences.Editor editor = prefs.edit();

editor.putString("key_name", "John Doe");
editor.putInt("key_age", 25);
editor.putBoolean("key_logged_in", true);
editor.putFloat("key_rating", 4.5f);
editor.putLong("key_time", System.currentTimeMillis());
editor.putStringSet("key_tags", new HashSet<>(Arrays.asList("android", "kotlin")));

// Remove
editor.remove("key_name");
editor.clear(); // ลบทั้งหมด

// Commit
editor.apply();  // Async - แนะนำสำหรับ UI Thread
editor.commit(); // Sync - คืนค่า boolean (success/fail) - ใช้ใน Background Thread

ตัวอย่างครบถ้วน: Login/Profile Screen

Layout (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp"
    android:gravity="center">

    <TextView
        android:id="@+id/tvStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Not Logged In"
        android:textSize="18sp"
        android:layout_marginBottom="24dp" />

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

    <EditText
        android:id="@+id/etPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Password"
        android:inputType="textPassword"
        android:layout_marginBottom="24dp" />

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

    <Button
        android:id="@+id/btnLogout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Logout"
        android:layout_marginTop="12dp" />

</LinearLayout>

MainActivity.java

import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private static final String PREFS_NAME = "auth_prefs";
    private static final String KEY_USERNAME = "username";
    private static final String KEY_IS_LOGGED_IN = "is_logged_in";
    private static final String KEY_TOKEN = "auth_token";

    private SharedPreferences prefs;
    private EditText etUsername, etPassword;
    private TextView tvStatus;
    private Button btnLogin, btnLogout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
        etUsername = findViewById(R.id.etUsername);
        etPassword = findViewById(R.id.etPassword);
        tvStatus = findViewById(R.id.tvStatus);
        btnLogin = findViewById(R.id.btnLogin);
        btnLogout = findViewById(R.id.btnLogout);

        updateUI();

        btnLogin.setOnClickListener(v -> attemptLogin());
        btnLogout.setOnClickListener(v -> logout());
    }

    private void attemptLogin() {
        String username = etUsername.getText().toString().trim();
        String password = etPassword.getText().toString().trim();

        if (username.isEmpty() || password.isEmpty()) {
            Toast.makeText(this, "กรุณากรอก Username และ Password", Toast.LENGTH_SHORT).show();
            return;
        }

        // จำลอง API Call (จริงๆ ควรใช้ Retrofit/Coroutine)
        if ("admin".equals(username) && "1234".equals(password)) {
            // Login Success - Save to SharedPreferences
            String fakeToken = "token_" + System.currentTimeMillis();
            
            prefs.edit()
                .putString(KEY_USERNAME, username)
                .putString(KEY_TOKEN, fakeToken)
                .putBoolean(KEY_IS_LOGGED_IN, true)
                .apply(); // Async

            Toast.makeText(this, "Login สำเร็จ", Toast.LENGTH_SHORT).show();
            updateUI();
        } else {
            Toast.makeText(this, "Username/Password ผิด", Toast.LENGTH_SHORT).show();
        }
    }

    private void logout() {
        prefs.edit()
            .remove(KEY_USERNAME)
            .remove(KEY_TOKEN)
            .putBoolean(KEY_IS_LOGGED_IN, false)
            .apply();
        
        Toast.makeText(this, "Logout แล้ว", Toast.LENGTH_SHORT).show();
        updateUI();
    }

    private void updateUI() {
        boolean loggedIn = prefs.getBoolean(KEY_IS_LOGGED_IN, false);
        if (loggedIn) {
            String username = prefs.getString(KEY_USERNAME, "User");
            tvStatus.setText("Logged in as: " + username);
            etUsername.setVisibility(View.GONE);
            etPassword.setVisibility(View.GONE);
            btnLogin.setVisibility(View.GONE);
            btnLogout.setVisibility(View.VISIBLE);
        } else {
            tvStatus.setText("Not Logged In");
            etUsername.setVisibility(View.VISIBLE);
            etPassword.setVisibility(View.VISIBLE);
            btnLogin.setVisibility(View.VISIBLE);
            btnLogout.setVisibility(View.GONE);
        }
    }
}

Best Practices และข้อควรระวัง

1. ใช้ Constants สำหรับ Keys

// ไม่ได้ Hardcode String ในโค้ด
public class PrefsKeys {
    public static final String USER_TOKEN = "user_token";
    public static final String USER_NAME = "user_name";
    // ...
}

2. Encrypt ข้อมูลสำคัญ (Security)

// ใช้ EncryptedSharedPreferences (Jetpack Security)
implementation "androidx.security:security-crypto:1.1.0-alpha03"

MasterKey masterKey = new MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build();

SharedPreferences prefs = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

3. ใช้ apply() แทน commit() ใน UI Thread

  • apply(): เขียน Async ไม่ Block UI, ไม่สนใจ Result
  • commit(): เขียน Sync Block Thread, คืนค่า Boolean

4. Listener สำหรับการเปลี่ยนแปลง

prefs.registerOnSharedPreferenceChangeListener((sharedPreferences, key) -> {
    if (KEY_IS_LOGGED_IN.equals(key)) {
        boolean loggedIn = sharedPreferences.getBoolean(key, false);
        // Update UI หรือ Notify อื่นๆ
    }
});

// Unregister ใน onDestroy
@Override
protected void onDestroy() {
    super.onDestroy();
    prefs.unregisterOnSharedPreferenceChangeListener(listener);
}

5. Backup/Restore (Auto Backup)





     
    
    

SharedPreferences vs DataStore vs Room

FeatureSharedPreferencesPreferences DataStoreRoom Database
Data TypePrimitive + StringSetPrimitive + StringSetAny Object (Entity)
Async API❌ (apply async แต่ read sync)✅ Flow/RxJava✅ Flow/RxJava/Suspend
Type Safety❌❌✅ Compile-time
Query/Index❌❌✅ SQL Query
MigrationManualAuto (Simple)Auto (Complex)
Size Limit~1-2 MB~1-2 MBLarge (Disk)
Use CaseSimple Settings, FlagsModern Settings, ProtoApp Data, Cache, Offline

สรุป

  • SharedPreferences เหมาะสำหรับ ข้อมูลเล็ก, Simple, Key-Value
  • ใช้ MODE_PRIVATE เสมอ
  • Key ใช้ Constants, Value ใช้ apply()
  • ข้อมูล Sensitve ใช้ EncryptedSharedPreferences
  • โปรเจกต์ใหม่/Complex Data → ใช้ DataStore (Proto) หรือ Room