TODO 프로젝트
카테고리: Project
Flutter로 Todo 앱을 만드는 것은 Flutter의 기본적인 기능들을 익히기에 좋은 프로젝트입니다. 여기에는 기본적인 화면 구성, 상태 관리, 데이터 저장 등이 포함됩니다. 단계별로 진행해 보겠습니다.
1. Flutter 프로젝트 생성
터미널에서 새로운 Flutter 프로젝트를 생성합니다.
flutter create todo_app
cd todo_app
2. 의존성 추가
pubspec.yaml
파일을 열고 필요한 패키지를 추가합니다. 예를 들어, 상태 관리를 위한 provider
패키지와, 로컬 데이터 저장을 위한 shared_preferences
패키지를 사용할 수 있습니다.
dependencies:
flutter:
sdk: flutter
provider: ^6.1.3
shared_preferences: ^2.0.13
패키지를 설치합니다.
flutter pub get
3. 데이터 모델 만들기
lib/models/todo.dart
파일을 생성하고 Todo 아이템의 모델을 정의합니다.
class Todo {
final String id;
final String title;
final bool isDone;
Todo({
required this.id,
required this.title,
this.isDone = false,
});
Todo copyWith({
String? id,
String? title,
bool? isDone,
}) {
return Todo(
id: id ?? this.id,
title: title ?? this.title,
isDone: isDone ?? this.isDone,
);
}
}
4. 상태 관리 및 데이터 저장
lib/providers/todo_provider.dart
파일을 생성하고 상태 관리와 데이터 저장을 위한 클래스를 작성합니다.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/todo.dart';
class TodoProvider with ChangeNotifier {
List<Todo> _todos = [];
List<Todo> get todos => _todos;
TodoProvider() {
_loadTodos();
}
void addTodo(Todo todo) {
_todos.add(todo);
notifyListeners();
_saveTodos();
}
void toggleTodoStatus(String id) {
final todoIndex = _todos.indexWhere((todo) => todo.id == id);
if (todoIndex >= 0) {
final todo = _todos[todoIndex];
_todos[todoIndex] = todo.copyWith(isDone: !todo.isDone);
notifyListeners();
_saveTodos();
}
}
void _saveTodos() async {
final prefs = await SharedPreferences.getInstance();
final todosJson = _todos.map((todo) => {
'id': todo.id,
'title': todo.title,
'isDone': todo.isDone,
}).toList();
prefs.setString('todos', todosJson.toString());
}
void _loadTodos() async {
final prefs = await SharedPreferences.getInstance();
final todosJson = prefs.getString('todos');
if (todosJson != null) {
final todosList = List<Map<String, dynamic>>.from(todosJson as Iterable);
_todos = todosList.map((json) => Todo(
id: json['id'],
title: json['title'],
isDone: json['isDone'],
)).toList();
notifyListeners();
}
}
}
5. UI 작성
lib/main.dart
파일을 열고 UI를 작성합니다.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/todo_provider.dart';
import 'models/todo.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => TodoProvider(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: TodoListScreen(),
),
);
}
}
class TodoListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final todoProvider = Provider.of<TodoProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text('Todo App'),
),
body: ListView.builder(
itemCount: todoProvider.todos.length,
itemBuilder: (context, index) {
final todo = todoProvider.todos[index];
return ListTile(
title: Text(todo.title),
trailing: Checkbox(
value: todo.isDone,
onChanged: (value) {
todoProvider.toggleTodoStatus(todo.id);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
String title = '';
return AlertDialog(
title: Text('Add Todo'),
content: TextField(
onChanged: (value) {
title = value;
},
decoration: InputDecoration(hintText: 'Enter todo title'),
),
actions: [
TextButton(
onPressed: () {
if (title.isNotEmpty) {
todoProvider.addTodo(Todo(
id: DateTime.now().toString(),
title: title,
));
Navigator.of(context).pop();
}
},
child: Text('Add'),
),
],
);
},
);
},
child: Icon(Icons.add),
),
);
}
}
코드 분석 설명 해석
1. Flutter 프로젝트 생성 및 패키지 추가
프로젝트를 생성하고 필요한 패키지를 추가하는 부분은 잘 이해하셨으리라 생각합니다. provider
패키지는 상태 관리를 도와주고, shared_preferences
는 데이터를 로컬에 저장하는 데 사용됩니다.
2. 데이터 모델 (lib/models/todo.dart
)
class Todo {
final String id;
final String title;
final bool isDone;
Todo({
required this.id,
required this.title,
this.isDone = false,
});
Todo copyWith({
String? id,
String? title,
bool? isDone,
}) {
return Todo(
id: id ?? this.id,
title: title ?? this.title,
isDone: isDone ?? this.isDone,
);
}
}
Todo
클래스: Todo 항목을 정의합니다.id
: 각 Todo 항목의 고유 식별자입니다.title
: Todo의 제목입니다.isDone
: Todo가 완료되었는지를 나타내는 불리언 값입니다.
copyWith
메서드: 현재 Todo 객체를 복사하여 일부 속성만 변경할 때 사용됩니다. 이는 불변 객체(immutable object)를 관리하는 데 유용합니다.
3. 상태 관리 및 데이터 저장 (lib/providers/todo_provider.dart
)
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/todo.dart';
class TodoProvider with ChangeNotifier {
List<Todo> _todos = [];
List<Todo> get todos => _todos;
TodoProvider() {
_loadTodos();
}
void addTodo(Todo todo) {
_todos.add(todo);
notifyListeners();
_saveTodos();
}
void toggleTodoStatus(String id) {
final todoIndex = _todos.indexWhere((todo) => todo.id == id);
if (todoIndex >= 0) {
final todo = _todos[todoIndex];
_todos[todoIndex] = todo.copyWith(isDone: !todo.isDone);
notifyListeners();
_saveTodos();
}
}
void _saveTodos() async {
final prefs = await SharedPreferences.getInstance();
final todosJson = _todos.map((todo) => {
'id': todo.id,
'title': todo.title,
'isDone': todo.isDone,
}).toList();
prefs.setString('todos', todosJson.toString());
}
void _loadTodos() async {
final prefs = await SharedPreferences.getInstance();
final todosJson = prefs.getString('todos');
if (todosJson != null) {
final todosList = List<Map<String, dynamic>>.from(todosJson as Iterable);
_todos = todosList.map((json) => Todo(
id: json['id'],
title: json['title'],
isDone: json['isDone'],
)).toList();
notifyListeners();
}
}
}
TodoProvider
클래스: 상태 관리와 데이터 저장을 담당합니다._todos
: Todo 항목의 리스트를 저장합니다.todos
: 외부에서_todos
에 접근할 수 있는 getter입니다.- 생성자: 앱 시작 시
_loadTodos()
를 호출하여 저장된 Todo 항목을 로드합니다. addTodo
메서드: 새로운 Todo 항목을 추가하고, 상태를 업데이트하며, 변경 사항을 저장합니다.toggleTodoStatus
메서드: 특정 Todo의 상태를 토글하고, 상태를 업데이트하며, 변경 사항을 저장합니다._saveTodos
메서드: 현재의 Todo 리스트를 로컬 스토리지(Shared Preferences)에 저장합니다._loadTodos
메서드: 로컬 스토리지에서 Todo 리스트를 로드합니다.
4. UI 작성 (lib/main.dart
)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/todo_provider.dart';
import 'models/todo.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => TodoProvider(),
child: MaterialApp(
home: TodoListScreen(),
),
);
}
}
class TodoListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final todoProvider = Provider.of<TodoProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text('Todo App'),
),
body: ListView.builder(
itemCount: todoProvider.todos.length,
itemBuilder: (context, index) {
final todo = todoProvider.todos[index];
return ListTile(
title: Text(todo.title),
trailing: Checkbox(
value: todo.isDone,
onChanged: (value) {
todoProvider.toggleTodoStatus(todo.id);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
String title = '';
return AlertDialog(
title: Text('Add Todo'),
content: TextField(
onChanged: (value) {
title = value;
},
decoration: InputDecoration(hintText: 'Enter todo title'),
),
actions: [
TextButton(
onPressed: () {
if (title.isNotEmpty) {
todoProvider.addTodo(Todo(
id: DateTime.now().toString(),
title: title,
));
Navigator.of(context).pop();
}
},
child: Text('Add'),
),
],
);
},
);
},
child: Icon(Icons.add),
),
);
}
}
MyApp
클래스: 앱의 루트 위젯을 정의합니다.ChangeNotifierProvider
를 사용하여TodoProvider
를 전체 앱에서 사용할 수 있도록 설정합니다.
TodoListScreen
클래스: Todo 리스트를 보여주는 화면입니다.ListView.builder
: Todo 항목을 동적으로 리스트로 표시합니다.ListTile
: 각 Todo 항목을 리스트의 항목으로 표시합니다. 제목과 상태를 나타내는 체크박스를 포함합니다.FloatingActionButton
: 새로운 Todo 항목을 추가할 수 있는 버튼입니다. 버튼을 클릭하면 다이얼로그가 열려 Todo 항목을 추가할 수 있습니다.
댓글 남기기