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

บันทึก ArrayList ลง SharedPreferences ใน Android


SharedPreferences และ ArrayList

SharedPreferences เก็บข้อมูล Key-Value แบบ Primitive (String, Int, Boolean, Float, Long, StringSet) ไม่รองรับ ArrayList/List โดยตรง วิธีแก้คือ แปลง ArrayList → JSON String → เก็บใน SharedPreferences

วิธีที่ 1: ใช้ Gson (แนะนำ - ทรงพลังที่สุด)

Dependency

dependencies {
    implementation 'com.google.code.gson:gson:2.10.1'
}

Helper Class: SharedPrefsManager.java

import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

public class SharedPrefsManager {
    private static final String PREFS_NAME = "app_prefs";
    private static final String KEY_USER_LIST = "user_list";
    
    private final SharedPreferences prefs;
    private final Gson gson;
    private final Type listType = new TypeToken>() {}.getType();

    public SharedPrefsManager(Context context) {
        prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
        gson = new Gson();
    }

    // Save List
    public void saveUserList(List userList) {
        String json = gson.toJson(userList);
        prefs.edit().putString(KEY_USER_LIST, json).apply();
    }

    // Load List
    public List getUserList() {
        String json = prefs.getString(KEY_USER_LIST, null);
        if (json == null) return new ArrayList<>();
        return gson.fromJson(json, listType);
    }

    // Add Single Item
    public void addUser(User user) {
        List list = getUserList();
        list.add(user);
        saveUserList(list);
    }

    // Clear
    public void clearUserList() {
        prefs.edit().remove(KEY_USER_LIST).apply();
    }
}

Model Class: User.java

public class User {
    private String name;
    private String email;
    private int age;

    // Constructors, Getters, Setters
    public User(String name, String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }
    // ... getters/setters
}

ใช้งานใน Activity

public class MainActivity extends AppCompatActivity {
    private SharedPrefsManager prefsManager;
    private EditText etName, etEmail, etAge;
    private TextView tvResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        prefsManager = new SharedPrefsManager(this);
        etName = findViewById(R.id.etName);
        etEmail = findViewById(R.id.etEmail);
        etAge = findViewById(R.id.etAge);
        tvResult = findViewById(R.id.tvResult);

        findViewById(R.id.btnSave).setOnClickListener(v -> saveUser());
        findViewById(R.id.btnLoad).setOnClickListener(v -> loadUsers());
    }

    private void saveUser() {
        String name = etName.getText().toString().trim();
        String email = etEmail.getText().toString().trim();
        String ageStr = etAge.getText().toString().trim();

        if (name.isEmpty() || email.isEmpty() || ageStr.isEmpty()) {
            Toast.makeText(this, "กรอกข้อมูลให้ครบ", Toast.LENGTH_SHORT).show();
            return;
        }

        User user = new User(name, email, Integer.parseInt(ageStr));
        prefsManager.addUser(user);
        Toast.makeText(this, "บันทึกแล้ว", Toast.LENGTH_SHORT).show();
        clearInputs();
    }

    private void loadUsers() {
        List users = prefsManager.getUserList();
        if (users.isEmpty()) {
            tvResult.setText("ยังไม่มีข้อมูล");
            return;
        }
        
        StringBuilder sb = new StringBuilder();
        for (User u : users) {
            sb.append(u.getName()).append(" - ").append(u.getEmail()).append(" (").append(u.getAge()).append(")").append("\n");
        }
        tvResult.setText(sb.toString());
    }
}

วิธีที่ 2: ใช้ StringSet (สำหรับ String List เท่านั้น)

SharedPreferences รองรับ Set<String> ตั้งแต่ API 11 (Honeycomb)

// Save
Set stringSet = new HashSet<>(stringList);
prefs.edit().putStringSet("string_list_key", stringSet).apply();

// Load
Set savedSet = prefs.getStringSet("string_list_key", new HashSet<>());
List list = new ArrayList<>(savedSet); // Order ไม่รับประกัน!
> ⚠️ **ข้อจำกัด:** ไม่รองรับ Object, ไม่รองรับ Duplicate, **ไม่รับประกัน Order** (HashSet)

วิธีที่ 3: Manual JSON (ไม่ใช้ Library)

// แปลง ArrayList เป็น JSON Array String แบบ Manual
public String listToJson(List list) {
    StringBuilder sb = new StringBuilder("[");
    for (int i = 0; i < list.size(); i++) {
        sb.append('"').append(list.get(i).replace("\"", "\\\"")).append('"');
        if (i < list.size() - 1) sb.append(",");
    }
    sb.append("]");
    return sb.toString();
}

// Parse กลับ (Simple - ไม่รองรับ Escape ทุกกรณี)
public List jsonToList(String json) {
    List list = new ArrayList<>();
    if (json == null || json.length() < 2) return list;
    String content = json.substring(1, json.length() - 1); // remove []
    String[] items = content.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)"); // Split by comma not in quotes
    for (String item : items) {
        String trimmed = item.trim();
        if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
            trimmed = trimmed.substring(1, trimmed.length() - 1);
        }
        list.add(trimmed.replace("\\\"", "\""));
    }
    return list;
}
> ❌ **ไม่แนะนำ** ใช้แค่กรณีห้ามเพิ่ม Dependency (Gson เล็กมาก ~200KB คุ้มค่า)

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

1. ขนาดข้อมูล (Size Limit)

  • SharedPreferences เก็บใน XML File บน Internal Storage
  • ไม่ควรเกิน 1-2 MB (อ่าน/เขียนช้า, ANR Risk)
  • List ใหญ่/Object ใหญ่ → ใช้ Room Database หรือ DataStore

2. Thread Safety

  • apply() = Async (Non-blocking) - ใช้ใน UI Thread ได้
  • commit() = Sync (Blocking) - ไม่ควรใช้ใน UI Thread
  • Gson toJson/fromJson ใช้เวลา → ให้รันบน Background Thread (Coroutines/RxJava)

3. TypeToken สำหรับ Generic

// ถูกต้อง
Type listType = new TypeToken>() {}.getType();

// ผิด (Type Erasure)
Type listType = new TypeToken>() {}.getType(); // จะเป็น List

4. Migration ไป DataStore (Modern)

Jetpack DataStore (Preferences/DataStore) แทน SharedPreferences:

// build.gradle
implementation "androidx.datastore:datastore-preferences:1.0.0"
// หรือ Proto DataStore สำหรับ Object/Complex Type
implementation "androidx.datastore:datastore:1.0.0"

// Proto DataStore ใช้ Protocol Buffers รองรับ List/Object ได้โดยตรง

สรุป

วิธีเหมาะสำหรับข้อดีข้อเสีย
Gson + JSON StringObject List, Complex DataType Safety, Flexible, Standardต้องเพิ่ม Library, Serialize/Deserialize Cost
StringSetString List เท่านั้นBuilt-in, ไม่ต้อง LibraryNo Order, No Duplicate, String Only
Manual JSONห้ามเพิ่ม DependencyZero DependencyError-prone, Maintenance Hell
Room/DataStoreProduction App, Large DataRobust, Query, Observe, Type-safeSetup ซับซ้อนกว่า

แนะนำ: ใช้ Gson + SharedPreferences สำหรับข้อมูลเล็ก/Simple List, ย้ายไป Room/Proto DataStore เมื่อข้อมูลใหญ่หรือซับซ้อน