๋ก์ปฌ ์ ์ฅ์ - shared_preferences, sqflite/drift
ํ ๋ง ๊ธ์์ ์ฑ ์์ ์ค์์น๋ก ๋คํฌ ๋ชจ๋๋ฅผ ๋ฐ๊ฟจ๋๋ฐ, ์ฑ์ ๊ป๋ค ์ผ๋ฉด ๋ค์ ์ฒ์ ์ํ๋ก ๋์๊ฐ๋ค๊ณ ํ๋ค. ๊ฐ์ ๋ณ์์ ๋ด์๋๊ธฐ ๋๋ฌธ์ด๋ค. ๋ณ์๋ ์ฑ์ด ์คํ๋๋ ๋์๋ง ๋ฉ๋ชจ๋ฆฌ์ ์๋ค.
class _MyAppState extends State<MyApp> {
ThemeMode themeMode = ThemeMode.system; // ์ฑ์ ๋๋ฉด ์ฌ๋ผ์ง
void toggle(bool isDark) {
setState(() {
themeMode = isDark ? ThemeMode.dark : ThemeMode.light;
});
}
}์ค์์น๋ฅผ ์ผ๊ณ ์ฑ์ ์์ ํ ์ข
๋ฃํ ๋ค ๋ค์ ์คํํ๋ฉด ThemeMode.system์ผ๋ก ๋์๊ฐ๋ค. ์ฌ์ฉ์ ์
์ฅ์์๋ "์ค์ ์ด ์ ์ฅ์ด ์ ๋๋" ์ฑ์ด๋ค. ์ฑ์ ๊บผ๋ ๋จ์์ผ ํ๋ ๊ฐ์ ๊ธฐ๊ธฐ์ ์ ์ฅ์์ ์จ์ผ ํ๋ค.
์์ธ: ๋ฉ๋ชจ๋ฆฌ์ ์๋ ๊ฐ์ ์ฑ์ด ์ฃฝ์ผ๋ฉด ๊ฐ์ด ์ฌ๋ผ์ง๋ค
์ ์ฅ์๋ ํฌ๊ฒ ๋ ๊ฐ์ง๋ก ๋๋๋ค. ์ค์ ๊ฐ ๋ช ๊ฐ์ฒ๋ผ ์๊ณ ๋จ์ํ ๊ฐ์ ํค-๊ฐ ์ ์ฅ์(shared_preferences), ๋ชฉ๋ก์ฒ๋ผ ์ฌ๋ฌ ์ค์ ๋ฐ์ดํฐ๋ฅผ ์กฐ๊ฑด์ผ๋ก ์ฐพ์์ผ ํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค(sqflite, drift)๋ฅผ ์ด๋ค.
ํด๊ฒฐ 1: shared_preferences๋ก ์ค์ ๊ฐ ์ ์ฅํ๊ธฐ
flutter pub add shared_preferencesshared_preferences๋ int, double, bool, String, ๋ฌธ์์ด ๋ฆฌ์คํธ๋ฅผ ํค์ ํจ๊ป ์ ์ฅํ๋ค. ์๋ก ์์ํ๋ค๋ฉด ๊ณต์ ๋ฌธ์๊ฐ ๊ถํ๋ SharedPreferencesAsync๋ฅผ ์ด๋ค.
import 'package:shared_preferences/shared_preferences.dart';
final prefs = SharedPreferencesAsync();
// ์ ์ฅ
await prefs.setBool('isDark', true);
// ์ฝ๊ธฐ
final isDark = await prefs.getBool('isDark') ?? false;setBool('ํค', ๊ฐ): ํค์ ๊ฐ์ ์ ์ฅํ๋ค.setString,setInt๋ ์๋คgetBool('ํค'): ํค์ ๊ฐ์ ์ฝ๋๋ค. ์ ์ฅ๋ ์ ์ด ์์ผ๋ฉดnull์ด๋ผ์Future<bool?>๋ฅผ ๋๋ ค์ค๋ค?? false: ์ฒ์ ์คํ์ฒ๋ผ ๊ฐ์ด ์์ ๋๋ฅผ ๋๋นํ ๊ธฐ๋ณธ๊ฐ์ด๋ค. null safety ๊ธ์??๊ฐ ๊ทธ๋๋ก ์ฐ์ธ๋ค
์ ์ฅ๊ณผ ์ฝ๊ธฐ ๋ชจ๋ Future๋ผ์ await๋ฅผ ๋ถ์ฌ์ผ ํ๋ค. ๋นผ๋จน์ผ๋ฉด Future<bool?>๋ฅผ bool์ฒ๋ผ ์ฐ๋ ค๋ค ํ์
์๋ฌ๊ฐ ๋๋ค.
์ฑ์ด ์์ํ ๋ ์ ์ฅ๋ ๊ฐ์ ์ฝ์ด์ ๋๊ฒจ์ฃผ๋ฉด ๊ป๋ค ์ผ๋ ์ค์ ์ด ์ ์ง๋๋ค.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final prefs = SharedPreferencesAsync();
final isDark = await prefs.getBool('isDark') ?? false;
runApp(MyApp(isDark: isDark));
}๊ฐ์ ๋ฐ๊ฟ ๋๋ setState์ ํจ๊ป ์ ์ฅ๋ ํ๋ค.
void toggle(bool isDark) async {
setState(() {
themeMode = isDark ? ThemeMode.dark : ThemeMode.light;
});
await SharedPreferencesAsync().setBool('isDark', isDark);
}์ฝ์ง ํฌ์ธํธ 1: main์์ await๋ฅผ ์ฐ๋ ค๋๋ฐ ๋ฐ์ธ๋ฉ ์๋ฌ๊ฐ ๋๋ค
runApp ์ ์ ํ๋ฌ๊ทธ์ธ์ ์ฐ๋ฉด Flutter๊ฐ ์์ง ์ค๋น๋์ง ์์๋ค๋ ์ทจ์ง์ ์๋ฌ๊ฐ ๋๋ค. main์ async๋ก ๋ฐ๊พธ๊ณ ๋งจ ์์ WidgetsFlutterBinding.ensureInitialized();๋ฅผ ๋ฃ๋๋ค. shared_preferences์ฒ๋ผ ๊ธฐ๊ธฐ ๊ธฐ๋ฅ์ ์ฐ๋ ํ๋ฌ๊ทธ์ธ์ ์ฑ ์์ ์ ์ ๋ถ๋ฅผ ๋ ํ์ํ ํ ์ค์ด๋ค.
์ฝ์ง ํฌ์ธํธ 2: ์ ๋ฐฉ์๊ณผ ์ ๋ฐฉ์์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ์ง ์๋๋ค
์์ ๊ธ์๋ SharedPreferences.getInstance()๊ฐ ๋ง์ด ๋ณด์ธ๋ค. ์ด ๋ฐฉ์๋ ์์ง ์์ง๋ง, ์ API(SharedPreferencesAsync, SharedPreferencesWithCache)๋ ์ ์ฅ ์์น๊ฐ ๋ฌ๋ผ์ ์ ๋ฐฉ์์ผ๋ก ์ ์ฅํ ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ์ฝ์ง ๋ชปํ๋ค. ๊ณต์ ๋ฌธ์๊ฐ ์ง์ ์ฎ๊ธฐ๋ ๊ณผ์ ์ด ํ์ํ๋ค๊ณ ๊ฒฝ๊ณ ํ๋ค. ์ด๋ฏธ ๋ฐฐํฌํ ์ฑ์ ๋ฐ๊ฟ ๋๋ ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ฎ๊ธฐ๋ ์ฝ๋๋ฅผ ๋จผ์ ํ์ธํด์ผ ํ๊ณ , ์ ์ฑ์ด๋ผ๋ฉด ์ฒ์๋ถํฐ ์ API๋ก ์์ํ๋ฉด ๋๋ค.
์ฝ์ง ํฌ์ธํธ 3: ๊ฐ์ฒด๋ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฃ๊ณ ์ถ์ด์ง๋ค
shared_preferences๋ ์์ ์ ์ ๋ช ๊ฐ์ง ํ์
๋ง ์ ์ฅํ๋ค. User ๊ฐ์ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๋ ค๋ฉด ์ ๊ธ์์ ๋ง๋ toJson์ผ๋ก JSON ๋ฌธ์์ด๋ก ๋ฐ๊ฟ setString์ ๋ฃ๋ ๋ฐฉ๋ฒ์ด ์๋ค. ํ์ง๋ง ํญ๋ชฉ์ด ์ฌ๋ฌ ๊ฐ๋ก ๋๊ณ "์กฐ๊ฑด์ผ๋ก ์ฐพ๊ธฐ"๊ฐ ํ์ํด์ง๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ๊ฐ๋ ๊ฒ ๋ง๋ค. ๋ shared_preferences๋ ์ํธํ๋์ง ์์ ํ๋ฒํ ์ ์ฅ์๋ผ์ ๋ก๊ทธ์ธ ํ ํฐ์ด๋ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฃ์ผ๋ฉด ์ ๋๋ค. ๊ทธ๋ฐ ๊ฐ์ ๋ณด์ ์ ์ฅ์ ํจํค์ง(flutter_secure_storage)๋ฅผ ์ด๋ค.
ํด๊ฒฐ 2: ์ฌ๋ฌ ์ค์ ๋ฐ์ดํฐ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์
ํ ์ผ ๋ชฉ๋ก์ฒ๋ผ ํญ๋ชฉ์ด ๋์ด๋๊ณ , "์๋ฃ ์ ๋ ๊ฒ๋ง", "์ ๋ชฉ์ผ๋ก ๊ฒ์" ๊ฐ์ ์กฐ๊ฑด์ด ํ์ํ๋ฉด SQLite ๊ธฐ๋ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ด๋ค. ์ ํ์ง๋ ๋์ด๋ค.
sqflite: SQL ๋ฌธ์์ด์ ์ง์ ์จ์ ์คํํ๋ ์ ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค.db.insert('todos', {'title': '์ฐ์ ์ฌ๊ธฐ'})์ฒ๋ผ ์ฐ๊ณ , ๊ฒฐ๊ณผ๋Map์ ๋ชฉ๋ก์ผ๋ก ๋ฐ๋๋ค. ๊ฐ๋จํ์ง๋ง SQL ๋ฌธ์์ด๊ณผ ์ปฌ๋ผ ์ด๋ฆ์ ์คํ๋ฅผ ์ปดํ์ผ๋ฌ๊ฐ ๋ชป ์ก๋๋คdrift: ํ ์ด๋ธ์ Dart ํด๋์ค๋ก ์ ์ํ๊ณ ํ์ ์ด ์๋ ์ฝ๋๋ก ์ง์ํ๋ค. ์ ๊ธ์์ ๋ณธ ์ฝ๋ ์์ฑ(build_runner)์ ์ด๋ค. ์ด ๊ธ์ ์ด์ชฝ์ผ๋ก ๊ฐ๋ค
flutter pub add drift drift_flutter
flutter pub add dev:drift_dev
flutter pub add dev:build_runnerํ ์ด๋ธ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ ์ํ๋ค.
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
part 'database.g.dart';
class Todos extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text()();
BoolColumn get done => boolean().withDefault(const Constant(false))();
}
@DriftDatabase(tables: [Todos])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(driftDatabase(name: 'app_db'));
@override
int get schemaVersion => 1;
Future<List<Todo>> getAll() => select(todos).get();
Stream<List<Todo>> watchAll() => select(todos).watch();
Future<int> add(String title) =>
into(todos).insert(TodosCompanion.insert(title: title));
Future<int> remove(int id) =>
(delete(todos)..where((t) => t.id.equals(id))).go();
}class Todos extends Table: ํ์ ์ปฌ๋ผ์ Dart๋ก ์ ๋๋ค.id๋ ์๋ ์ฆ๊ฐ,title์ ๊ธ์,done์ ๊ธฐ๋ณธ๊ฐ์ดfalse์ธ ์ฐธ/๊ฑฐ์ง์ด๋ค@DriftDatabase(tables: [Todos]): ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์ด๋ค ํ๋ฅผ ์ฐ๋์ง ์๋ ค์ค๋ค._$AppDatabase,Todo,TodosCompanion์ ์์ฑ๊ธฐ๊ฐ ๋ง๋ค์ด์ฃผ๋ ์ฝ๋๋คschemaVersion: ํ ๊ตฌ์กฐ์ ๋ฒ์ ์ด๋ค. ๊ตฌ์กฐ๋ฅผ ๋ฐ๊ฟ ๋๋ง๋ค ์ฌ๋ฆฐ๋คselect(todos).get(): ์ ์ฒด๋ฅผ ํ ๋ฒ ์ฝ๋๋คselect(todos).watch(): ๊ฐ์ด ๋ฐ๋ ๋๋ง๋ค ์ ๋ชฉ๋ก์ ํ๋ ค์ฃผ๋Stream์ด๋คinto(todos).insert(TodosCompanion.insert(title: title)): ํ ์ค ์ถ๊ฐํ๋ค.Companion์ "์์งid๊ฐ ์๋ ์ ํญ๋ชฉ"์ ๋ด๋ ์์ฑ๋ ํด๋์ค๋ค(delete(todos)..where(...)).go(): ์กฐ๊ฑด์ ๋ง๋ ์ค์ ์ง์ด๋ค...๋ ์์ ๊ฐ์ฒด์ ์กฐ๊ฑด์ ๋ถ์ด๊ณ ๊ทธ ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ์ด์ด๊ฐ๋ ๋ฌธ๋ฒ์ด๋ค
์ ์๋ฅผ ์ด ๋ค dart run build_runner build๋ก database.g.dart๋ฅผ ์์ฑํ๋ค. ์ด ๊ธ์ watchAll()์ด ๋๋ ค์ฃผ๋ Stream์ ๋ค์ ๊ธ์ StreamBuilder์์ ๋ฐ๋ก ํ๋ฉด์ ์ฐ๊ฒฐํ๋ค. ํญ๋ชฉ์ ์ถ๊ฐํ๊ฑฐ๋ ์ง์ฐ๋ฉด ๋ชฉ๋ก์ด ์๋์ผ๋ก ๊ฐฑ์ ๋๋ค.
์ฝ์ง ํฌ์ธํธ: ํ๋ฅผ ๊ณ ์ณค๋๋ ๊ธฐ์กด ์ฌ์ฉ์ ์ฑ์ด ์ฃฝ๋๋ค
๊ฐ๋ฐ ์ค์๋ ์ฑ์ ์ง์ฐ๊ณ ๋ค์ ์ค์นํ๋ฉด ๋์ง๋ง, ์ด๋ฏธ ์ค์นํ ์ฌ์ฉ์์๊ฒ๋ ์ด์ ๊ตฌ์กฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ์ผ์ด ๋จ์ ์๋ค. ํ์ ์ปฌ๋ผ์ ์ถ๊ฐํ๊ณ schemaVersion๋ง ์ฌ๋ฆฌ๊ณ ์ฎ๊ธฐ๋ ๋ฐฉ๋ฒ(๋ง์ด๊ทธ๋ ์ด์
)์ ์ ์ ์ผ๋ฉด ๊ธฐ์กด ์ฌ์ฉ์์๊ฒ์ ๋ฌธ์ ๊ฐ ์๊ธด๋ค. ๊ตฌ์กฐ๋ฅผ ๋ฐ๊ฟ ๋๋ ๋ฒ์ ์ ์ฌ๋ฆฌ๊ณ ์ด์ ๋ฒ์ ์์ ์ ๊ตฌ์กฐ๋ก ๋ฐ๊พธ๋ ์ฝ๋๋ฅผ ํจ๊ป ์์ฑํด์ผ ํ๋ค.
๋ part 'database.g.dart'๊ฐ ์๊ฑฐ๋ ์์ฑ๊ธฐ๋ฅผ ๋ค์ ์ ๋๋ฆฌ๋ฉด _$AppDatabase๋ฅผ ์ฐพ์ ์ ์๋ค๋ ์๋ฌ๊ฐ ๋๋ค. JSON ๊ธ์์ ๋ณธ ๊ฒ๊ณผ ๊ฐ์ ์ํฉ์ด๋ค.
Android, iOS์ ๋น๊ตํ๋ฉด
// Kotlin - DataStore, Room
val isDark = dataStore.data.map { it[DARK_KEY] ?: false }
@Entity data class Todo(@PrimaryKey(autoGenerate = true) val id: Int, val title: String)
@Dao interface TodoDao { @Query("SELECT * FROM todo") fun watchAll(): Flow<List<Todo>> }// Swift - UserDefaults, SwiftData
UserDefaults.standard.set(true, forKey: "isDark")
@Model class Todo { var title: String; init(title: String) { self.title = title } }- ๊ฐ๋จํ ์ค์ ๊ฐ: Flutter
shared_preferences/ AndroidDataStore(์SharedPreferences) / iOSUserDefaults - ํ ์ ์: Flutter
Tableํด๋์ค / Room@Entity/ SwiftData@Model, Core Data ์ํฐํฐ - ์ง์: Flutter๋
select(todos)๊ฐ์ Dart ์ฝ๋ / Room์@Dao์ SQL / SwiftData๋@Query - ๋ณ๊ฒฝ ์๋ฆผ: Flutter
watch()์Stream/ Room์Flow/ SwiftData๋@Query๊ฐ ์๋ - ์ฝ๋ ์์ฑ: Flutter
build_runner, Room์ kapt/KSP
Room๊ณผ ๊ฐ์ฅ ๋ฎ์๋ค. Table์ด @Entity, AppDatabase๊ฐ @Database, ์ง์ ๋ฉ์๋๊ฐ @Dao, watch()์ Stream์ด Flow์ ํด๋นํ๋ค. ์๋๋ก์ด๋ ์ด๋ฆ๊ณผ ๊ฐ์ SharedPreferences๋ ์๋๋ฐ, ์ ์๋๋ก์ด๋ SharedPreferences์ ๊ฐ์ ๊ณ์ด์ด๋ค.
์ ๋ฆฌ
- ๋ณ์์ ๋ด์ ๊ฐ์ ์ฑ์ด ๊บผ์ง๋ฉด ์ฌ๋ผ์ง๋ฏ๋ก, ๋จ๊ฒจ์ผ ํ๋ ๊ฐ์ ๊ธฐ๊ธฐ์ ์ ์ฅ์์ ์ด๋ค
- ์ค์ ๊ฐ ๋ช ๊ฐ๋
shared_preferences์SharedPreferencesAsync๋ก ์ ์ฅํ๊ณ , ์ฝ์ ๋๋ ๊ฐ์ด ์์ ์ ์์ด??๋ก ๊ธฐ๋ณธ๊ฐ์ ์ค๋ค main์์await๋ก ํ๋ฌ๊ทธ์ธ์ ์ฐ๋ ค๋ฉดWidgetsFlutterBinding.ensureInitialized()๊ฐ ํ์ํ๋ค- ์
SharedPreferences์ ์ API๋ ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ๊ณต์ ํ์ง ์๊ณ , ๋น๋ฐ๋ฒํธ๋ ํ ํฐ์shared_preferences์ ๋ฃ์ง ์๋๋ค - ์ฌ๋ฌ ์ค์ ๋ฐ์ดํฐ๋ฅผ ์กฐ๊ฑด์ผ๋ก ์ฐพ์์ผ ํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฐ๊ณ ,
sqflite๋ SQL์ ์ง์ ์ฐ๋ ๋ฐฉ์,drift๋ Dart ์ฝ๋๋ก ์ง์ํ๋ ๋ฐฉ์์ด๋ค drift๋Table,@DriftDatabase๋ก ์ ์ํ๊ณbuild_runner๋ก ์์ฑํ๋ฉฐ,watch()๋ ๊ฐ์ด ๋ฐ๋ ๋๋ง๋ค ์๋ ค์ฃผ๋Stream์ด๋ค- ํ ๊ตฌ์กฐ๋ฅผ ๋ฐ๊ฟ ๋๋
schemaVersion์ ์ฌ๋ฆฌ๊ณ ๊ธฐ์กด ๋ฐ์ดํฐ๋ฅผ ์ฎ๊ธฐ๋ ๋ง์ด๊ทธ๋ ์ด์ ๋ ํจ๊ป ์ด๋ค
๋ค์ ๊ธ์์๋ Future์ Stream์ ํ๋ฉด์ ์ฐ๊ฒฐํ๋ FutureBuilder, StreamBuilder, AsyncValue๋ฅผ ๋ค๋ค๋ณด๊ฒ ๋ค.
