로컬 저장소 — SharedPreferences, Hive, SQLite
로컬 저장소 — SharedPreferences, Hive, SQLite
앱에서 데이터를 기기에 저장해야 하는 경우가 많습니다. 설정값, 캐시, 오프라인 데이터 등 용도에 따라 적합한 저장소를 선택해야 합니다.
저장소 선택 기준
| 저장소 | 용도 | 데이터 형태 | 성능 |
|---|---|---|---|
| SharedPreferences | 설정값, 토큰 | Key-Value (단순) | 빠름 |
| Hive | 구조화된 캐시 | Key-Value (복잡) | 매우 빠름 |
| SQLite | 관계형 데이터 | 테이블 기반 | 복잡한 쿼리에 강함 |
SharedPreferences
간단한 키-값 쌍을 저장합니다. 앱 설정, 첫 실행 여부, 로그인 토큰 등에 적합합니다.
dependencies:
shared_preferences: ^2.2.0
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보다 복잡한 데이터를 저장할 수 있고, 성능이 매우 좋습니다.
dependencies:
hive: ^2.2.0
hive_flutter: ^1.1.0
dev_dependencies:
hive_generator: ^2.0.0
build_runner: ^2.4.0
기본 사용
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');
}
커스텀 객체 저장
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로 실시간 반영
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가 적합합니다.
dependencies:
sqflite: ^2.3.0
path: ^1.9.0
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 — 민감한 데이터
dependencies:
flutter_secure_storage: ^9.0.0
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 패턴으로 추상화하면 나중에 교체가 쉽습니다
댓글 로딩 중...