지금까지 만든 To-do 앱은 앱을 종료하면 데이터가 사라지는 단점이 있었습니다.
이를 해결하기 위해 Flutter에서 가장 간단하게 사용할 수 있는 저장 방법,
바로 SharedPreferences를 사용해봅니다.
🔍 SharedPreferences란?
안드로이드/IOS에서 제공하는 키-값 저장 방식의 간단한 로컬 DB입니다.
- 앱 내 간단한 설정값, 문자열 리스트 등을 저장할 수 있음
- 파일 기반으로 동작하며, 앱 삭제 전까지 데이터가 유지됨
🧩 목표 기능
✅ 할 일을 추가하면 로컬에 저장
✅ 앱을 껐다 켜도 목록 유지
✅ 삭제, 체크 등 변경 시에도 자동 저장
📦 SharedPreferences 설치
1️⃣ pubspec.yaml에 패키지 추가
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.2.2
flutter:
sdk: flutter
shared_preferences: ^2.2.2
2️⃣ 설치 명령 실행
flutter pub get
🛠️ 구조 변경 – Map을 JSON으로 변환
SharedPreferences는 단순한 자료형만 저장할 수 있어서
List<Map> → JSON 문자열로 변환해서 저장해야 합니다.
우리는 아래 형태를 사용합니다:
List<Map<String, dynamic>> → List<String> (JSON으로 변환)
💻 전체 코드 예제 (로컬 저장 포함)
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MaterialApp(home: TodoStorageApp()));
class TodoStorageApp extends StatefulWidget {
@override
_TodoStorageAppState createState() => _TodoStorageAppState();
}
class _TodoStorageAppState extends State<TodoStorageApp> {
final TextEditingController _controller = TextEditingController();
List<Map<String, dynamic>> _todoList = [];
@override
void initState() {
super.initState();
_loadTodos();
}
// 저장
Future<void> _saveTodos() async {
final prefs = await SharedPreferences.getInstance();
List<String> jsonList =
_todoList.map((item) => jsonEncode(item)).toList();
await prefs.setStringList('todos', jsonList);
}
// 불러오기
Future<void> _loadTodos() async {
final prefs = await SharedPreferences.getInstance();
List<String>? jsonList = prefs.getStringList('todos');
if (jsonList != null) {
setState(() {
_todoList =
jsonList.map((item) => jsonDecode(item) as Map<String, dynamic>).toList();
});
}
}
void _addTodo() {
String text = _controller.text.trim();
if (text.isNotEmpty) {
setState(() {
_todoList.add({'title': text, 'done': false});
_controller.clear();
});
_saveTodos();
}
}
void _toggleDone(int index) {
setState(() {
_todoList[index]['done'] = !_todoList[index]['done'];
});
_saveTodos();
}
void _removeTodo(int index) {
setState(() {
_todoList.removeAt(index);
});
_saveTodos();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('로컬 저장 할 일 앱')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 입력창
Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(
labelText: '할 일을 입력하세요',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: _addTodo,
child: Text('추가'),
),
],
),
SizedBox(height: 20),
// 리스트
Expanded(
child: ListView.builder(
itemCount: _todoList.length,
itemBuilder: (context, index) {
final item = _todoList[index];
return Card(
child: ListTile(
leading: Checkbox(
value: item['done'],
onChanged: (_) => _toggleDone(index),
),
title: Text(
item['title'],
style: TextStyle(
decoration: item['done']
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => _removeTodo(index),
),
),
);
},
),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MaterialApp(home: TodoStorageApp()));
class TodoStorageApp extends StatefulWidget {
@override
_TodoStorageAppState createState() => _TodoStorageAppState();
}
class _TodoStorageAppState extends State<TodoStorageApp> {
final TextEditingController _controller = TextEditingController();
List<Map<String, dynamic>> _todoList = [];
@override
void initState() {
super.initState();
_loadTodos();
}
// 저장
Future<void> _saveTodos() async {
final prefs = await SharedPreferences.getInstance();
List<String> jsonList =
_todoList.map((item) => jsonEncode(item)).toList();
await prefs.setStringList('todos', jsonList);
}
// 불러오기
Future<void> _loadTodos() async {
final prefs = await SharedPreferences.getInstance();
List<String>? jsonList = prefs.getStringList('todos');
if (jsonList != null) {
setState(() {
_todoList =
jsonList.map((item) => jsonDecode(item) as Map<String, dynamic>).toList();
});
}
}
void _addTodo() {
String text = _controller.text.trim();
if (text.isNotEmpty) {
setState(() {
_todoList.add({'title': text, 'done': false});
_controller.clear();
});
_saveTodos();
}
}
void _toggleDone(int index) {
setState(() {
_todoList[index]['done'] = !_todoList[index]['done'];
});
_saveTodos();
}
void _removeTodo(int index) {
setState(() {
_todoList.removeAt(index);
});
_saveTodos();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('로컬 저장 할 일 앱')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 입력창
Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(
labelText: '할 일을 입력하세요',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: _addTodo,
child: Text('추가'),
),
],
),
SizedBox(height: 20),
// 리스트
Expanded(
child: ListView.builder(
itemCount: _todoList.length,
itemBuilder: (context, index) {
final item = _todoList[index];
return Card(
child: ListTile(
leading: Checkbox(
value: item['done'],
onChanged: (_) => _toggleDone(index),
),
title: Text(
item['title'],
style: TextStyle(
decoration: item['done']
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => _removeTodo(index),
),
),
);
},
),
),
],
),
),
);
}
}
🔐 핵심 포인트 요약
기능설명
| SharedPreferences.getInstance() | 저장소 인스턴스 가져오기 |
| setStringList() | 문자열 리스트 저장 |
| getStringList() | 문자열 리스트 불러오기 |
| jsonEncode / jsonDecode | Map ↔ String 변환 |
🎁 보너스 팁
- 다른 설정값 저장 시에도 활용 가능 (다크모드 여부 등)
- 사용자의 로그인 정보, 언어 설정, 앱 초기화 여부 등에도 적합
- JSON 구조가 복잡해진다면 hive나 sqflite 패키지를 고려해보세요
🧠 마무리 요약
개념설명
| SharedPreferences | Flutter에서 제공하는 로컬 저장소 |
| setState + 저장 | 상태가 바뀔 때마다 저장 |
| 앱 종료 후 유지 | 앱 재시작해도 데이터 유지됨 |
이제 여러분의 To-do 앱은 앱을 꺼도 데이터가 유지되는 실사용 가능한 앱이 되었습니다!