17. 일정 관리 앱 만들기
17.1 패키지 의존성
먼저 pubspec.yaml에서 의존성을 추가합니다.
dependencies:
flutter:
sdk: flutter
table_calendar: ^3.0.9
intl: ^0.18.1
intl은 날짜 형식을 출력할 때 필요하고, table_calendar는 달력 위젯입니다.
17.2 TableCalendar 기본 위젯 구조
이제 달력을 앱에 띄워봅시다. 간단한 StatefulWidget으로 작성합니다.
class CalendarScreen extends StatefulWidget {
const CalendarScreen({super.key});
@override
State<CalendarScreen> createState() => _CalendarScreenState();
}
class _CalendarScreenState extends State<CalendarScreen> {
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('일정 관리')),
body: Column(
children: [
TableCalendar(
focusedDay: _focusedDay,
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
calendarStyle: CalendarStyle(
selectedDecoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
),
Expanded(
child: Center(
child: Text(
_selectedDay == null
? '날짜를 선택해주세요'
: '${_selectedDay!.toLocal()}의 일정',
style: const TextStyle(fontSize: 16),
),
),
)
],
),
);
}
}
포인트
- selectedDayPredicate는 선택된 날짜를 알려주는 역할
- onDaySelected에서 setState를 통해 선택 날짜 업데이트
- 하단 Expanded 위젯으로 선택된 날짜를 표시
이렇게만 해도 달력과 선택한 날짜를 연결할 수 있어요.
17.3 일정 표시와 리스트뷰 결합
실제로는 선택된 날짜에 맞춘 일정 목록을 띄워야 하죠.
예를 들어 ListView.builder를 활용해 아래에 동적으로 일정을 그리면 됩니다.
List<String> _dummySchedules = [
'회의',
'스터디',
'헬스장',
'쇼핑',
];
Expanded(
child: ListView.builder(
itemCount: _dummySchedules.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_dummySchedules[index]),
);
},
),
),
- 이후 18장에서 이 데이터를 DB로 교체할 예정이기 때문에,
17장에서는 우선 하드코딩된 리스트로 테스트해도 충분합니다.
18장. 데이터베이스 적용하기
18.1 drift 패키지 설정
pubspec.yaml에 다음을 추가합니다.
dependencies:
drift: ^2.13.0
sqlite3_flutter_libs: ^0.5.17
path_provider: ^2.1.1
path: ^1.8.3
get_it: ^7.6.4
provider: ^6.0.5
dev_dependencies:
drift_dev: ^2.13.0
build_runner: ^2.4.6
18.2 DB 테이블 정의
drift_database.dart:
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'dart:io';
part 'drift_database.g.dart';
class Schedules extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get content => text()();
DateTimeColumn get date => dateTime()();
}
@DriftDatabase(tables: [Schedules])
class LocalDatabase extends _$LocalDatabase {
LocalDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
Stream<List<Schedule>> watchSchedules(DateTime date) {
return (select(schedules)
..where((tbl) => tbl.date.equals(date)))
.watch();
}
Future<int> createSchedule(SchedulesCompanion data) =>
into(schedules).insert(data);
Future<int> removeSchedule(int id) =>
(delete(schedules)..where((tbl) => tbl.id.equals(id))).go();
}
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dir = await getApplicationDocumentsDirectory();
final file = File(p.join(dir.path, 'db.sqlite'));
return NativeDatabase(file);
});
}
- Schedules 테이블에 id, content, date 3개의 컬럼
- watchSchedules는 날짜별 일정 스트림
- LazyDatabase는 실제 디스크에 파일로 저장
18.3 일정 CRUD 연동
예시:
ElevatedButton(
onPressed: () {
final newSchedule = SchedulesCompanion(
content: Value('새로운 일정'),
date: Value(_selectedDay!),
);
localDatabase.createSchedule(newSchedule);
},
child: const Text('일정 추가'),
),
삭제 기능 -> Dismissible을 쓰면 아주 직관적임.
Dismissible(
key: ValueKey(schedule.id),
background: Container(color: Colors.red),
onDismissed: (_) => localDatabase.removeSchedule(schedule.id),
child: ListTile(title: Text(schedule.content)),
)
19. 서버와 연동하기
19.1 Dio 기반 API 서비스 작성
- api_service.dart
import 'package:dio/dio.dart';
class ApiService {
final Dio _dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
Future<list> fetchSchedules(DateTime date) async {
final resp = await _dio.get('/schedules', queryParameters: {
'date': date.toIso8601String(),
});
return (resp.data as List)
.map((json) => ScheduleModel.fromJson(json))
.toList();
}
Future createSchedule(DateTime date, String content) async {
final resp = await _dio.post('/schedules', data: {
'date': date.toIso8601String(),
'content': content,
});
return ScheduleModel.fromJson(resp.data);
}
Future deleteSchedule(int id) async {
await _dio.delete('/schedules/$id');
}
}
</list
19.2 상태관리 Provider 설계
- ScheduleProvider 클래스 (예)
class ScheduleProvider extends ChangeNotifier {
final ApiService apiService;
List<ScheduleModel> schedules = [];
ScheduleProvider(this.apiService);
Future<void> load(DateTime date) async {
schedules = await apiService.fetchSchedules(date);
notifyListeners();
}
Future<void> add(DateTime date, String content) async {
final newSchedule = await apiService.createSchedule(date, content);
schedules.add(newSchedule);
notifyListeners();
}
Future<void> remove(int id) async {
await apiService.deleteSchedule(id);
schedules.removeWhere((s) => s.id == id);
notifyListeners();
}
}
- notifyListeners()로 UI 즉시 갱신
- optimistic UI를 구현할 수도 있는데,
예를 들어 추가할 때 바로 schedules.add 후에 서버 응답을 기다리는 식으로 구성하면 됩니다.
19.3 UI 연결
ChangeNotifierProvider를 이용해 앱에 공급:
ChangeNotifierProvider(
create: (_) => ScheduleProvider(ApiService()),
child: MyApp(),
)
'EDOC > Flutter 스터디 (2025-1)' 카테고리의 다른 글
| [3.7 ~ 3.9] 오늘도 출첵 ~ AI 채팅봇, 소울챗 (1) | 2025.05.21 |
|---|---|
| [3.4 ~ 3.6] 디지털 주사위 ~ 영상 통화 (1) | 2025.05.21 |
| [3.1 ~ 3.3] 블로그 웹 앱 콜백 함수, 웹뷰, 네이티브 설정 ~ 만난 지 며칠 U&I 상태 관리, CupertinoDatePicker, Dialog, DateTime (0) | 2025.05.20 |
| [2.6 ~ 2.7] 기본 위젯 알아보기 ~ 앱을 만들려면 알아야 하는 그 밖의 지식 (0) | 2025.04.12 |
| [1.4 ~ 2.5] 다트 3.0 신규 문법 / 플러터 입문하기 (0) | 2025.04.05 |