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

วิธีเก็บ HashMap ใน Singleton Pattern ใน Android

Singleton Pattern สำหรับเก็บข้อมูล HashMap แบบ Global

Singleton เป็น Design Pattern ที่จำกัดการสร้าง Instance เพียง 1 ตัวตลอดชีวิตของแอปพลิเคชัน เหมาะสำหรับการจัดการข้อมูลร่วมกัน (Shared State) เช่น Cache, Configuration, User Session บทความนี้สาธิตการเก็บ HashMap<String, Double> ใน Singleton

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

Android Studio → File → New Project → Empty Activity

ขั้นตอนที่ 2: Layout - activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="24dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Singleton HashMap Demo"
            android:textSize="20sp"
            android:textStyle="bold"
            android:layout_marginBottom="16dp" />

        <Button
            android:id="@+id/btnSave"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="บันทึกข้อมูลลง Singleton"
            android:layout_marginBottom="12dp" />

        <Button
            android:id="@+id/btnShow"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="แสดงข้อมูลจาก Singleton" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

ขั้นตอนที่ 3: Singleton Class - DataRepository.java

package com.example.singletondemo;

import android.content.Context;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class DataRepository {

    // 1. Private static instance (Lazy Initialization + Thread Safe)
    private static volatile DataRepository instance;
    private final Map<String, Double> dataMap = new HashMap<>();
    private Context appContext;

    // 2. Private Constructor
    private DataRepository() {}

    // 3. Public static getInstance() - Double-Checked Locking
    public static DataRepository getInstance() {
        if (instance == null) {
            synchronized (DataRepository.class) {
                if (instance == null) {
                    instance = new DataRepository();
                }
            }
        }
        return instance;
    }

    // 4. Initialize Context (เรียกครั้งเดียวใน Application class หรือ Activity แรก)
    public void init(Context context) {
        if (appContext == null) {
            this.appContext = context.getApplicationContext();
        }
    }

    public Context getContext() {
        return appContext;
    }

    // 5. Business Methods
    public void put(String key, Double value) {
        dataMap.put(key, value);
    }

    public Double get(String key) {
        return dataMap.get(key);
    }

    public void remove(String key) {
        dataMap.remove(key);
    }

    public void clear() {
        dataMap.clear();
    }

    public Map<String, Double> getAll() {
        // คืนค่า Unmodifiable Map เพื่อป้องกันการแก้ไขจากภายนอก
        return Collections.unmodifiableMap(dataMap);
    }

    public String getFormattedData() {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Double> entry : dataMap.entrySet()) {
            sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
        }
        return sb.length() > 0 ? sb.toString() : "ไม่มีข้อมูล";
    }
}

ขั้นตอนที่ 4: MainActivity.java

package com.example.singletondemo;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private DataRepository repository;
    private Button btnSave, btnShow;

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

        // Initialize Singleton
        repository = DataRepository.getInstance();
        repository.init(getApplicationContext());

        btnSave = findViewById(R.id.btnSave);
        btnShow = findViewById(R.id.btnShow);

        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // บันทึกข้อมูลตัวอย่าง
                repository.put("กระดาษ", 10.0);
                repository.put("เติมเงิน", 100.0);
                repository.put("ค่าสาธารณูปโภค", 500.0);
                repository.put("ค่าอาหาร", 250.50);

                Toast.makeText(MainActivity.this, "บันทึกข้อมูลเรียบร้อย", Toast.LENGTH_SHORT).show();
            }
        });

        btnShow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String data = repository.getFormattedData();
                Toast.makeText(MainActivity.this, data, Toast.LENGTH_LONG).show();
            }
        });
    }
}

Best Practices สำหรับ Singleton ใน Android

1. ใช้ Application Class สำหรับ Initialize Context

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        DataRepository.getInstance().init(this);
    }
}

ใน AndroidManifest.xml: <application android:name=".MyApplication" ...>

2. ใช้ Dependency Injection (Hilt/Koin) แทน Singleton โดยตรง

// Hilt Module
@Module
@InstallIn(SingletonComponent.class)
public class RepositoryModule {
    @Provides
    @Singleton
    DataRepository provideDataRepository() {
        return DataRepository.getInstance();
    }
}

// ใช้งาน
@Inject DataRepository repository;

3. Thread Safety

HashMap ไม่ Thread-Safe หากมีหลาย Thread เขียนพร้อมกัน ควรใช้:

  • ConcurrentHashMap แทน HashMap
  • หรือ `synchronized` ใน method put/get/clear

4. หลีกเลี่ยง Memory Leak

  • เก็บ Application Context เท่านั้น (ไม่ใช่ Activity Context)
  • ไม่ถือ Reference ของ View/Activity ใน Singleton

การทดสอบ

  1. รันแอป → กด "บันทึกข้อมูล"
  2. กด "แสดงข้อมูล" → จะแสดง Toast พร้อมข้อมูลทั้งหมด
  3. ปิดแอป → เปิดใหม่ → ข้อมูลยังคงอยู่ (เพราะ Process ยังไม่ตาย)
  4. Force Stop แอป → ข้อมูลจะหาย (เพราะ Process ถูก Kill)

สรุป

Singleton Pattern เหมาะสำหรับข้อมูลที่ต้องการเข้าถึงได้ทั่วแอป และมีชีวิตอยู่ร่วมกับ Process แต่ควรพิจารณา Dependency Injection และ Thread Safety ในโปรเจกต์จริง