로컬 저장소 — SharedPreferences, Hive, SQLite

앱에서 데이터를 기기에 저장해야 하는 경우가 많습니다. 설정값, 캐시, 오프라인 데이터 등 용도에 따라 적합한 저장소를 선택해야 합니다.


저장소 선택 기준

저장소용도데이터 형태성능
SharedPreferences설정값, 토큰Key-Value (단순)빠름
Hive구조화된 캐시Key-Value (복잡)매우 빠름
SQLite관계형 데이터테이블 기반복잡한 쿼리에 강함

SharedPreferences

간단한 키-값 쌍을 저장합니다. 앱 설정, 첫 실행 여부, 로그인 토큰 등에 적합합니다.

YAML
dependencies:
  shared_preferences: ^2.2.0
DART
import 'package:shared_preferences/shared_preferences.dart';

class SettingsService {
  // 저장
  Future<void> setDarkMode(bool value) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool('darkMode', value);
  }

  // 읽기
  Future<bool> getDarkMode() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getBool('darkMode') ?? false;
  }

  // 문자열 저장
  Future<void> setToken(String token) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('accessToken', token);
  }

  // 삭제
  Future<void> removeToken() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove('accessToken');
  }

  // 전체 삭제
  Future<void> clearAll() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.clear();
  }
}

지원하는 타입: int, double, bool, String, List<String>

주의: SharedPreferences는 암호화되지 않습니다. 비밀번호 같은 민감한 정보는 flutter_secure_storage를 사용하세요.


Hive — 빠른 NoSQL 저장소

Hive는 순수 Dart로 작성된 경량 NoSQL 데이터베이스입니다. SharedPreferences보다 복잡한 데이터를 저장할 수 있고, 성능이 매우 좋습니다.

YAML
dependencies:
  hive: ^2.2.0
  hive_flutter: ^1.1.0

dev_dependencies:
  hive_generator: ^2.0.0
  build_runner: ^2.4.0

기본 사용

DART
import 'package:hive_flutter/hive_flutter.dart';

// 초기화 (main에서)
void main() async {
  await Hive.initFlutter();
  await Hive.openBox('settings');
  runApp(const MyApp());
}

// 저장/읽기
class HiveSettingsService {
  final Box _box = Hive.box('settings');

  // 저장
  void setTheme(String theme) => _box.put('theme', theme);

  // 읽기
  String getTheme() => _box.get('theme', defaultValue: 'light');

  // 삭제
  void removeTheme() => _box.delete('theme');
}

커스텀 객체 저장

DART
import 'package:hive/hive.dart';

part 'todo.g.dart';

@HiveType(typeId: 0)
class Todo extends HiveObject {
  @HiveField(0)
  String title;

  @HiveField(1)
  bool isDone;

  @HiveField(2)
  DateTime createdAt;

  Todo({
    required this.title,
    this.isDone = false,
    required this.createdAt,
  });
}

// 어댑터 등록 (main에서)
Hive.registerAdapter(TodoAdapter());
await Hive.openBox<Todo>('todos');

// CRUD 사용
class TodoRepository {
  final Box<Todo> _box = Hive.box<Todo>('todos');

  List<Todo> getAll() => _box.values.toList();

  Future<void> add(Todo todo) async {
    await _box.add(todo);
  }

  Future<void> update(int index, Todo todo) async {
    await _box.putAt(index, todo);
  }

  Future<void> delete(int index) async {
    await _box.deleteAt(index);
  }
}

ValueListenableBuilder로 실시간 반영

DART
ValueListenableBuilder(
  valueListenable: Hive.box<Todo>('todos').listenable(),
  builder: (context, Box<Todo> box, _) {
    final todos = box.values.toList();
    return ListView.builder(
      itemCount: todos.length,
      itemBuilder: (context, index) {
        return ListTile(title: Text(todos[index].title));
      },
    );
  },
)

SQLite — 관계형 데이터베이스

복잡한 쿼리, 테이블 간 관계, 대량 데이터에는 SQLite가 적합합니다.

YAML
dependencies:
  sqflite: ^2.3.0
  path: ^1.9.0
DART
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  static Database? _database;

  Future<Database> get database async {
    _database ??= await _initDB();
    return _database!;
  }

  Future<Database> _initDB() async {
    final dbPath = await getDatabasesPath();
    final path = join(dbPath, 'app.db');

    return openDatabase(
      path,
      version: 1,
      onCreate: (db, version) async {
        // 테이블 생성
        await db.execute('''
          CREATE TABLE users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            email TEXT UNIQUE NOT NULL,
            created_at TEXT NOT NULL
          )
        ''');

        await db.execute('''
          CREATE TABLE posts (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            body TEXT,
            user_id INTEGER,
            FOREIGN KEY (user_id) REFERENCES users(id)
          )
        ''');
      },
      onUpgrade: (db, oldVersion, newVersion) async {
        // 마이그레이션 처리
        if (oldVersion < 2) {
          await db.execute('ALTER TABLE users ADD COLUMN age INTEGER');
        }
      },
    );
  }

  // 삽입
  Future<int> insertUser(Map<String, dynamic> user) async {
    final db = await database;
    return db.insert('users', user);
  }

  // 조회
  Future<List<Map<String, dynamic>>> getUsers() async {
    final db = await database;
    return db.query('users', orderBy: 'created_at DESC');
  }

  // 조건 조회
  Future<Map<String, dynamic>?> getUserById(int id) async {
    final db = await database;
    final results = await db.query(
      'users',
      where: 'id = ?',
      whereArgs: [id],
    );
    return results.isNotEmpty ? results.first : null;
  }

  // 업데이트
  Future<int> updateUser(int id, Map<String, dynamic> data) async {
    final db = await database;
    return db.update('users', data, where: 'id = ?', whereArgs: [id]);
  }

  // 삭제
  Future<int> deleteUser(int id) async {
    final db = await database;
    return db.delete('users', where: 'id = ?', whereArgs: [id]);
  }
}

flutter_secure_storage — 민감한 데이터

YAML
dependencies:
  flutter_secure_storage: ^9.0.0
DART
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorageService {
  final _storage = const FlutterSecureStorage();

  Future<void> saveToken(String token) async {
    await _storage.write(key: 'access_token', value: token);
  }

  Future<String?> getToken() async {
    return _storage.read(key: 'access_token');
  }

  Future<void> deleteToken() async {
    await _storage.delete(key: 'access_token');
  }
}

면접 포인트: "토큰을 어디에 저장하나요?"라는 질문에 SharedPreferences가 아닌 flutter_secure_storage 라고 답해야 합니다. SharedPreferences는 평문 저장이고, secure_storage는 iOS Keychain / Android Keystore를 사용합니다.


정리

  • 간단한 설정값은 SharedPreferences, 구조화된 데이터는 Hive, 관계형 데이터는 SQLite
  • 민감한 데이터(토큰, 비밀번호)는 반드시 flutter_secure_storage 사용
  • Hive는 순수 Dart라서 웹, 데스크톱에서도 동작합니다
  • SQLite는 복잡한 쿼리와 마이그레이션이 필요할 때 선택하세요
  • 저장소 접근은 Repository 패턴으로 추상화하면 나중에 교체가 쉽습니다
댓글 로딩 중...