π Today I Learned 25νμ°¨ - Flutter μΌμ κ΄λ¦¬ μ± λ§λ€κΈ° π
μ€λμ Flutterλ‘ μΌμ κ΄λ¦¬ μ±μ λ§λλ νλ‘μ νΈλ₯Ό μμνμ΅λλ€! λ¬λ ₯ UI ꡬνλΆν° λ°μ΄ν°λ² μ΄μ€ μ°λ, μλ² ν΅μ κΉμ§ λ¨κ³λ³λ‘ νμ΅νλ©° μ€λ¬΄μ νμν μ’ ν©μ μΈ μ± κ°λ° μλμ μ΅νκ² λ κ² κ°μ κΈ°λκ° λ©λλ€. π
π― νμ΅ λͺ©ν
- μΌμ κ΄λ¦¬ μ±μ μ 체 ꡬ쑰μ λ¨κ³λ³ κ°λ° κ³Όμ μ΄ν΄νκΈ°
- TableCalendar νλ¬κ·ΈμΈμ νμ©ν λ¬λ ₯ UI ꡬν
- Driftμ SQLiteλ₯Ό μ΄μ©ν λ‘컬 λ°μ΄ν°λ² μ΄μ€ ꡬμΆ
- ν€λ³΄λ λ ΈμΆ μ UI λμ λ°©λ² μ΅νκΈ°
- pubspec.yamlμ dependencies vs dev_dependencies μ°¨μ΄ μ΄ν΄
π νλ‘μ νΈ κ°μ
π λ―Έμ
μΌμ μ λ μ§μ μκ°λ³λ‘ μ μ₯νκ³ μ‘°ννλ μ± κ΅¬ννκΈ°
β μ£Όμ κΈ°λ₯
| κΈ°λ₯ | μ€λͺ |
|---|---|
| μΌμ μΆκ° | λ²νΌμ λλ¬ μλ‘μ΄ μΌμ μ λ ₯ νΌ μ΄κΈ° |
| μΌμ μμ± | νΌμ μ 보λ₯Ό μ λ ₯νμ¬ μ μΌμ λ§λ€κΈ° |
| μΌμ μ‘°ν | λ¬λ ₯μμ λ μ§λ₯Ό μ νν΄ ν΄λΉ λ μ§μ μΌμ νμΈ |
| μΌμ μΉ΄μ΄νΈ | μ νν λ μ§μ λ±λ‘λ μΌμ κ°μ νμ |
| λ°μ΄ν° μ μ₯ | SQLite λ‘컬 DB λλ μλ²μ μΌμ μ μ₯ |
π οΈ ν΅μ¬ κ΅¬μ± μμ
π¦ μΌμ κ΄λ¦¬ μ± κΈ°μ μ€ν
ββ π¨ UI λΌμ΄λΈλ¬λ¦¬
β ββ TableCalendar (λ¬λ ₯ μμ ―)
β ββ TextFormField (μ
λ ₯ νΌ)
β
ββ πΎ λ°μ΄ν° κ΄λ¦¬
β ββ SQLite (λ‘컬 λ°μ΄ν°λ² μ΄μ€)
β ββ Drift (SQL ORM)
β ββ Code Generation (μλ μ½λ μμ±)
β
ββ π λ€νΈμν¬ ν΅μ
ββ REST API
ββ Dio (HTTP ν΄λΌμ΄μΈνΈ)
π λ¨κ³λ³ κ°λ° λ‘λλ§΅
νλ‘μ νΈλ μ μ§μ μΌλ‘ λ°μ νλ κ΅¬μ‘°λ‘ μ§νλ©λλ€:
1λ¨κ³: λ©λͺ¨λ¦¬ μ μ₯
β
2λ¨κ³: λ‘컬 λ°μ΄ν°λ² μ΄μ€ (SQLite + Drift)
β
3λ¨κ³: μλ² λ°μ΄ν°λ² μ΄μ€ μ°λ (REST API)
β
4λ¨κ³: Firestore μ°λ
β
5λ¨κ³: JWT μΈμ¦ ꡬν
β
6λ¨κ³: μμ
λ‘κ·ΈμΈ & Firebase μΈμ¦
β
7λ¨κ³: Supabase μ°λ
β
8λ¨κ³: κ΄κ³ λ° λ°°ν¬
π‘ νμ΅ ν¬μΈνΈ: κ°μ μ±μ μ¬λ¬ λ°©μμΌλ‘ ꡬννλ©΄μ κ° κΈ°μ μ μ₯λ¨μ μ λΉκ΅ν μ μμ΅λλ€!
π» TableCalendar νλ¬κ·ΈμΈ
π κ°μ
TableCalendarλ Flutterμμ κ°μ₯ λ§μ΄ μ¬μ©λλ λ¬λ ₯ νλ¬κ·ΈμΈμΌλ‘, λ€μν 컀μ€ν°λ§μ΄μ§ μ΅μ μ μ 곡ν©λλ€.
β‘ μ£Όμ κΈ°λ₯
| κΈ°λ₯ | μ€λͺ |
|---|---|
| νΉμ λ μ§ μ ν | λ¨μΌ λ μ§ μ ν λ° ν¬μ»€μ€ |
| λ μ§ λ²μ μ ν | μμμΌκ³Ό μ’ λ£μΌμ μ§μ ν λ²μ μ ν |
| λ μ§ μ§μ νκΈ° | νμ¬ νλ©΄μ νμλ μ°/μ μ€μ |
| μ΄λ²€νΈ/μΌμ νμ | κ° λ μ§μ λ§μ»€λ λ°°μ§ νμ |
π κΈ°λ³Έ μ¬μ©λ²
// TableCalendar κΈ°λ³Έ ꡬν
import 'package:table_calendar/table_calendar.dart';
TableCalendar(
// 첫 λ²μ§Έ λ μ§ (λ¬λ ₯μ μμ λ²μ)
firstDay: DateTime.utc(2020, 1, 1),
// λ§μ§λ§ λ μ§ (λ¬λ ₯μ λ λ²μ)
lastDay: DateTime.utc(2030, 12, 31),
// ν¬μ»€μ€λ λ μ§ (νμ¬ νμλλ μ)
focusedDay: DateTime.now(),
// μ νλ λ μ§
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
// λ μ§ μ ν μ μ½λ°±
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
)
π§ pubspec.yaml μ€μ
π¦ Dependencies vs Dev Dependencies
νλ‘μ νΈ μ€μ νμΌμΈ pubspec.yamlμμ ν¨ν€μ§λ₯Ό μΆκ°ν λ λ κ°μ§ μΉμ
μ΄ μμ΅λλ€:
| κ΅¬λΆ | μ©λ | ν¨ν€μ§ | μμ |
|---|---|---|---|
| dependencies | μ± μ€νμ νμν ν¨ν€μ§ | β ν¬ν¨λ¨ | table_calendar, dio, drift |
| dev_dependencies | κ°λ° μμλ§ νμν λꡬ | β μ μΈλ¨ | build_runner, drift_dev, flutter_test |
π‘ μ½λ μμ± (Code Generation)
Driftμ κ°μ λΌμ΄λΈλ¬λ¦¬λ μ½λ μμ± κΈ°λ₯μ μ 곡ν©λλ€:
dependencies:
drift: ^2.0.0 # μ€μ μ±μμ μ¬μ©
sqlite3_flutter_libs: ^0.5.0
dev_dependencies:
drift_dev: ^2.0.0 # μ½λ μμ±μ© (κ°λ° λ¨κ³μμλ§)
build_runner: ^2.3.0 # μ½λ μμ± μ€νκΈ°
μ½λ μμ±μ΄λ?
- Dart μΈμ΄λ‘ ν μ΄λΈ μ μλ₯Ό μμ±νλ©΄
- SQL μΏΌλ¦¬κ° μλμΌλ‘ μμ±λλ κΈ°λ₯
- μ§μ SQLμ μμ±νμ§ μμλ λμ΄ μμ°μ±κ³Ό μμ μ± ν₯μ
# μ½λ μμ± μ€ν λͺ
λ Ήμ΄
flutter pub run build_runner build
# λ³κ²½μ¬ν κ°μ§νμ¬ μλ μ¬μμ±
flutter pub run build_runner watch
β οΈ μ£Όμ: μ½λ μμ±μ DB κ΅¬μ‘°κ° λ³κ²½λ λλ§λ€ μ€νν΄μΌ νμ§λ§, μ± μ€ν μμλ νμ μμΌλ―λ‘
dev_dependenciesμ ν¬ν¨ν©λλ€.
π¨ UI μ΄μ ν΄κ²°
β¨οΈ ν€λ³΄λ λ ΈμΆ μ μμ ― κ°λ¦Ό λ¬Έμ
λ¬Έμ μν©:
- μ¬μ©μκ°
TextFormFieldλ₯Ό ννλ©΄ ν€λ³΄λκ° νλ©΄ νλ¨μμ μ¬λΌμ΅λλ€ - ν€λ³΄λκ° μ λ ₯ νλλ λ²νΌμ κ°λ €μ μ¬μ©μ±μ΄ λ¨μ΄μ§λλ€
ν΄κ²° λ°©λ²:
MediaQueryλ₯Ό μ¬μ©νμ¬ ν€λ³΄λ λμ΄λ§νΌ νλ©΄μ μλ‘ μ΄λμν΅λλ€.
π» ꡬν μ½λ
// ν€λ³΄λ λμ΄λ₯Ό κ³ λ €ν BottomSheet ꡬν
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
return SafeArea(
child: Container(
// νλ©΄ λμ΄μ μ λ° + ν€λ³΄λ λμ΄
height: MediaQuery.of(context).size.height / 2 + bottomInset,
color: Colors.white,
child: Padding(
// μλμͺ½ ν¨λ©μ ν€λ³΄λ λμ΄λ§νΌ μΆκ°
padding: EdgeInsets.only(
left: 8,
right: 8,
top: 8,
bottom: bottomInset, // π ν΅μ¬: ν€λ³΄λ λμ΄λ§νΌ ν¨λ© μΆκ°
),
child: Column(
children: [
// μ
λ ₯ νΌ μμ ―λ€
TextFormField(
decoration: InputDecoration(labelText: 'μΌμ μ λͺ©'),
),
// ... κΈ°ν μμ ―λ€
],
),
),
),
);
π μ½λ μμΈ μ€λͺ
// 1οΈβ£ μμ€ν
μ΄ μ°¨μ§νλ νλ¨ μμ ν¬κΈ° κ°μ Έμ€κΈ°
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
// π± μΌλ°μ μΌλ‘ μ΄ κ° = ν€λ³΄λ λμ΄
// 2οΈβ£ Container λμ΄ λμ μ‘°μ
height: MediaQuery.of(context).size.height / 2 + bottomInset
// ν€λ³΄λκ° λνλλ©΄ Containerκ° μλμΌλ‘ λμμ§
// 3οΈβ£ νλ¨ ν¨λ© μΆκ°λ‘ 컨ν
μΈ λ₯Ό μλ‘ μ΄λ
padding: EdgeInsets.only(bottom: bottomInset)
// ν€λ³΄λκ° μ¬λΌμ¨ λ§νΌ 컨ν
μΈ λ μλ‘ μ΄λ
π λμ μ리
| μν | bottomInset | Container λμ΄ | κ²°κ³Ό |
|---|---|---|---|
| ν€λ³΄λ μ¨κΉ | 0 | νλ©΄ μ λ° | μΌλ° BottomSheet |
| ν€λ³΄λ νμ | ~300px | νλ©΄ μ λ° + 300px | μ λ ₯ νλκ° ν€λ³΄λ μλ‘ μ΄λ |
π― BottomSheet νμνκΈ°
// showModalBottomSheetλ‘ μΌμ μ
λ ₯ νΌ νμ
showModalBottomSheet(
context: context,
// ν€λ³΄λκ° μ¬λΌμ¬ λ BottomSheetλ κ°μ΄ μ¬λΌκ°λλ‘ μ€μ
isScrollControlled: true,
builder: (context) {
return ScheduleBottomSheet(); // μμμ ꡬνν μμ ―
},
);
π‘ ν:
isScrollControlled: trueλ₯Ό μ€μ νλ©΄ BottomSheet λμ΄λ₯Ό μμ λ‘κ² μ‘°μ ν μ μμ΅λλ€!
π λ§λ¬΄λ¦¬
β μ€λ λ°°μ΄ κ²
- νλ‘μ νΈ κ΅¬μ‘°: μΌμ κ΄λ¦¬ μ±μ μ 체 μν€ν μ²μ 8λ¨κ³ λ°μ κ³Όμ
- TableCalendar: Flutter λ¬λ ₯ νλ¬κ·ΈμΈμ κΈ°λ³Έ μ¬μ©λ²κ³Ό μ£Όμ κΈ°λ₯
- ν¨ν€μ§ κ΄λ¦¬:
dependenciesμdev_dependenciesμ μ°¨μ΄μ - μ½λ μμ±: Driftμ μλ SQL μμ± μ리μ
build_runnerμ¬μ©λ² - UI λμ:
MediaQuery.viewInsetsλ₯Ό νμ©ν ν€λ³΄λ λ ΈμΆ μ΄μ ν΄κ²°
π λ€μ κ³ν
- λ¬λ ₯ UI μμ±: TableCalendar 컀μ€ν°λ§μ΄μ§ λ° μ΄λ²€νΈ λ§μ»€ νμ
- μ λ ₯ νΌ κ΅¬ν: μ λͺ©, λ΄μ©, μκ° μ ν κΈ°λ₯μ΄ ν¬ν¨λ μμ ν μΌμ μ λ ₯ νλ©΄
- λ©λͺ¨λ¦¬ μ μ₯: μ± λ΄ λ©λͺ¨λ¦¬μ μΌμ λ°μ΄ν° μ μ₯ λ° μ‘°ν κΈ°λ₯ ꡬν
- Drift νμ΅: SQLite λ°μ΄ν°λ² μ΄μ€ μ€κ³ λ° Drift ORM μ¬μ©λ² μ΅νκΈ°
λκΈλ¨κΈ°κΈ°