บทที่ 13mobile|11 KB

บทที่ 13: Local Storage (SharedPreferences)

บทที่ 13: Local Storage (SharedPreferences)

🎯 เป้าหมายการเรียนรู้

หลังจากเรียนบทนี้แล้ว คุณจะสามารถ:

  • เข้าใจความแตกต่างระหว่าง Local Storage และ Database
  • ใช้ SharedPreferences เพื่อเก็บค่าการตั้งค่า (Settings) และสถานะการล็อกอิน (Session)
  • บันทึกและดึงข้อมูลประเภทต่างๆ (String, int, bool, JSON)
  • สร้าง Service Class เพื่อจัดการข้อมูลในเครื่องอย่างเป็นระบบ

📚 ทฤษฎีและแนวคิด

1. Local Storage vs Database

  • SharedPreferences: เปรียบเสมือนสมุดจดเล่มเล็กๆ เก็บข้อมูลง่ายๆ เช่น "เปิดเสียงไหม = ใช่", "ชื่อผู้ใช้ = สมชาย" เหมาะสำหรับ Key-Value pairs
  • Database (SQLite/Hive): เปรียบเสมือนตู้เก็บเอกสารขนาดใหญ่ เก็บข้อมูลซับซ้อน เช่น "รายการบันทึก 1,000 รายการ"

2. เมื่อไหร่ควรใช้ SharedPreferences?

✅ เก็บ Token การเข้าสู่ระบบ ✅ เก็บค่า Settings (Dark Mode, ภาษา) ✅ เก็บ Flag เล็กๆ น้อยๆ (เช่น "เคยเปิด Intro แล้วหรือยัง")

❌ ไม่ควรใช้เก็บ: ข้อมูลขนาดใหญ่ (รูปภาพ), รายการที่มีความสัมพันธ์กันซับซ้อน


💻 การเขียนโค้ด (Implementation)

1. ติดตั้ง Package

# pubspec.yaml
dependencies:
  shared_preferences: ^2.2.0

2. สร้าง Session Manager

ไฟล์: mobile/lib/services/session.dart

เราจะสร้าง Class นี้เพื่อจัดการข้อมูล "การเข้าสู่ระบบ" ของผู้ใช้โดยเฉพาะ

import 'dart:convert'; // ใช้แปลง Object <-> String
import 'package:shared_preferences/shared_preferences.dart';

class Session {

อธิบายทีละบรรทัด (Imports):

  • import 'dart:convert'

    • ใช้สำหรับแปลง Object เป็น String (Encode) ก่อนบันทึกลงเครื่อง
  • import 'package:shared_preferences/shared_preferences.dart'

    • Library หลักสำหรับอ่าน/เขียนข้อมูลลง Disk ของมือถือ (iOS Uses NSUserDefaults, Android uses SharedPreferences)

อธิบาย: // ตัวแปร static เพื่อเก็บค่าไว้ใน RAM ให้เรียกใช้ได้เร็วๆ ไม่ต้องอ่านจาก Disk ตลอด static String? token; static Map<String, dynamic>? user;

// --- 1. Initialization (เริ่มต้นทำงาน) --- // เรียกฟังก์ชันนี้ตอนเปิดแอป (Splash Screen) static Future<void> init() async { // ขอ instance จากระบบ (เปรียบเหมือนขอลิ้นชักส่วนตัว) final prefs = await SharedPreferences.getInstance();

// โหลดข้อมูลใส่ RAM
token = prefs.getString('auth_token');

// ข้อมูล User ถูกเก็บเป็น JSON String ต้องแปลงกลับเป็น Map
final userStr = prefs.getString('auth_user');
if (userStr != null) {
  try {
    user = jsonDecode(userStr);
  } catch (e) {
    print("Error decoding user: $e");
  }
}

}

// --- 2. Save (บันทึกข้อมูล) --- // เรียกเมื่อ Login สำเร็จ static Future<void> save(String newToken, Map<String, dynamic> newUser) async { // 1. อัปเดตใน RAM ก่อน (เพื่อให้แอปใช้งานได้ทันที) token = newToken; user = newUser;

// 2. บันทึกลง Disk (เพื่อให้ปิดแอปแล้วข้อมูลไม่หาย)
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', newToken);

// Object เก็บลงตรงๆ ไม่ได้ ต้องแปลงเป็น JSON String ก่อน
await prefs.setString('auth_user', jsonEncode(newUser));

}

// --- 3. Clear (ล้างข้อมูล) --- // เรียกเมื่อ Logout static Future<void> clear() async { token = null; user = null;

final prefs = await SharedPreferences.getInstance();
// ลบเฉพาะ Key ที่เราต้องการ
await prefs.remove('auth_token');
await prefs.remove('auth_user');

// หรือถ้าจะลบเกลี้ยงทั้งแอปใช้: await prefs.clear();

}

// Helper เช็คว่า Login อยู่ไหม static bool get isLoggedIn => token != null; }


### 3. สร้าง Theme Controller (จัดการการตั้งค่า)

ไฟล์: `mobile/lib/services/theme_controller.dart`

ตัวอย่างการใช้ SharedPreferences เก็บการตั้งค่าที่ไม่ใช่ Security

```dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class ThemeController {
  // Singleton Pattern: ให้ทั้งแอปมี Controller ตัวเดียว
  static final ThemeController instance = ThemeController._();
  ThemeController._();

  // ValueNotifier: ตัวแปรที่บอก UI ให้รีเฟรชได้เมื่อค่าเปลี่ยน
  final ValueNotifier<ThemeMode> themeMode = ValueNotifier(ThemeMode.system);

  Future<void> init() async {
    final prefs = await SharedPreferences.getInstance();
    // อ่านค่าที่บันทึกไว้ (คืนค่ามาเป็น String เช่น "dark", "light")
    final saved = prefs.getString('theme_mode');
    
    if (saved != null) {
      // แปลง String กลับเป็น ThemeMode Enum
      themeMode.value = ThemeMode.values.firstWhere(
        (e) => e.name == saved,
        orElse: () => ThemeMode.system, // ค่าเริ่มต้นถ้าหาไม่เจอ
      );
    }
  }

  Future<void> setTheme(ThemeMode mode) async {
    // 1. เปลี่ยนค่าในตัวแปร -> UI เปลี่ยนทันที
    themeMode.value = mode;
    
    // 2. บันทึกลงเครื่อง -> ปิดเปิดใหม่ค่าไม่หาย
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('theme_mode', mode.name); // เก็บชื่อ Enum (เช่น "dark")
  }
}

4. การใช้งานจริง (Splash Screen)

ไฟล์: mobile/lib/screens/splash_screen.dart

Future<void> _checkAuth() async {
  // 1. เริ่มต้นโหลดข้อมูลจาก Disk
  await Session.init();
  await ThemeController.instance.init();

  // 2. เช็คว่ามี Token ไหม?
  if (Session.isLoggedIn) {
     // ไปหน้าหลัก
  } else {
     // ไปหน้า Login
  }
}

5. ชนิดข้อมูลที่เก็บได้ (Supported Types)

SharedPreferences รองรับข้อมูลพื้นฐานเท่านั้น:

prefs.setString('username', 'Somchai');
prefs.setInt('score', 100);
prefs.setBool('is_active', true);
prefs.setDouble('height', 175.5);
prefs.setStringList('tags', ['work', 'life']); // เก็บ Array ของ String ได้

ถ้าต้องการเก็บ Object ซับซ้อน (เช่น DateTime, Custom Class) ต้องแปลงเป็น String หรือ Int ก่อน (เช่นเก็บเป็น timestamp, json string)


📝 แบบฝึกหัด

โจทย์ที่ 1: "พึ่งเคยเปิดครั้งแรก" (First Launch)

สิ่งที่ต้องทำ:

  1. สร้าง Key ชื่อ is_first_launch
  2. ใน Splash Screen ให้เช็คว่าค่านี้เป็น true หรือ false (default คือ null ให้ถือว่าเป็น true)
  3. ถ้าเป็นครั้งแรก ให้พาไปหน้า "Onboarding" (แนะนำแอพ)
  4. เมื่อดู Onboarding จบ ให้บันทึกค่าเป็น false

โจทย์ที่ 2: จำภาษาล่าสุด (Language Persistance)

สิ่งที่ต้องทำ:

  1. เพิ่ม logic ใน LanguageController (บทที่ 16)
  2. ใช้ prefs.setString('language_code', 'th') เมื่อเปลี่ยนภาษา
  3. ใช้ prefs.getString('language_code') ตอนเปิดแอปเพื่อคืนค่า

โจทย์ที่ 3: ระบบ Favorite ชั่วคราว

สิ่งที่ต้องทำ:

  1. ใช้ setStringList('favorite_ids')
  2. เมื่อกดหัวใจที่ Note ให้เพิ่ม ID ลงใน List แล้วบันทึก
  3. เมื่อเปิดแอป ให้โหลด List นี้มาเช็คว่า Note ไหนเป็น Favorite บ้าง

📌 สรุป

SharedPreferences คือเพื่อนแท้ของนักพัฒนา Mobile App

  • ง่าย: Key-Value คู่กัน
  • เร็ว: เหมาะกับข้อมูลเล็กๆ
  • ถาวร: ปิดแอป เปิดเครื่องใหม่ ข้อมูลยังอยู่

ไฟล์ที่เกี่ยวข้อง


🔜 บทถัดไป

บทที่ 14: Speech-to-Text Integration ถึงเวลาของฟีเจอร์พระเอก! เราจะมาทำระบบแปลงเสียงเป็นข้อความกัน