[Flutter 10์ผ์ฐจ๐Ÿ’•] ์ผ์ • ์‚ญ์ œ ๊ธฐ๋Šฅ๊ณผ ์ผ์ • ๊ฐœ์ˆ˜ ์นด์šดํŠธ ํ•˜๊ธฐ

ttt00ยท2026๋…„ 5์›” 27์ผ

Flutter

๋ชฉ๋ก ๋ณด๊ธฐ
11/11
post-thumbnail

์˜ค๋Š˜์˜ ๋ชฉํ‘œ : ์ผ์ • ์‚ญ์ œ ๊ธฐ๋Šฅ๊ณผ 0๊ฐœ๋กœ ๊ณ ์ •๋˜์–ด ์žˆ๋Š” ์ผ์ • ๊ฐœ์ˆ˜ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

ํ•ต์‹ฌ ๊ฐœ๋…

createState() : ์œ„์ ฏ์ด ๋นŒ๋“œํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” StatefulWidget ์‚ฌ์šฉ ์‹œ ํ•„์ˆ˜ ๊ตฌํ˜„ํ•˜๋Š” ๋ฉ”์†Œ๋“œ
DateTime : ๋‚ ์งœ, ์‹œ๊ฐ„ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค
Scaffold : ๊ธฐ๋ณธ ๋ผˆ๋Œ€ ์œ„์ ฏ
floatingActionButton : ์šฐ์ธก ํ•˜๋‹จ์— ๋– ์žˆ๋Š” ๋ฒ„ํŠผ
โญ๏ธStreamBuilderโญ๏ธ : ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ํ™”๋ฉด์— ์—…๋ฐ์ดํŠธํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์œ„์ ฏ

์ˆ˜์—… ์ฝ”๋“œ

/screen/home_screen.dart

import 'package:flutter/material.dart';
import 'package:calendar_scheduler/component/main_calendar.dart';
import 'package:calendar_scheduler/component/schedule_card.dart';
import 'package:calendar_scheduler/component/today_banner.dart';
import 'package:calendar_scheduler/component/schedule_bottom_sheet.dart';
import 'package:calendar_scheduler/const/colors.dart';
import 'package:get_it/get_it.dart';
import 'package:calendar_scheduler/database/drift_database.dart';


class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen>{
  DateTime selectedDate = DateTime.utc(

    DateTime.now().year,
    DateTime.now().month,
    DateTime.now().day,
  );

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        backgroundColor: PRIMARY_COLOR,
        onPressed: (){
          showModalBottomSheet(
              context: context,
              isDismissible : true,
              builder: (_) =>ScheduleBottomSheet(
                selectedDate: selectedDate,
              ),
              isScrollControlled: true, // ํ™”๋ฉด ์ตœ๋Œ€ ๋†’์ด๋ฅผ : ํ™”๋ฉด ์ „์ฒด๋กœ ๋ณ€๊ฒฝ

          );
        },
        child: Icon(
          Icons.add,
        ),
      ),

      body: SafeArea(
          child: Column(
            children: [
              MainCalendar(
                selectedDate: selectedDate,
                //์„ ํƒ๋œ ๋‚ ์งœ ์ „๋‹ฌ ์ฝ”๋“œ

                onDaySelected: onDaySelected,
              ),
              SizedBox(height:8),
              StreamBuilder<List<Schedule>>(
                stream: GetIt.I<LocalDatabase>().watchSchedules(selectedDate),
                builder: (context, snapshoot){
                  return TodayBanner(
                      selectedDate: selectedDate,
                      count: snapshoot.data?.length ?? 0,
                  );
                },
              ),
              SizedBox(height:8),

              Expanded( //์ปฌ๋Ÿผ์—์„œ ๋‚จ์€ ๊ณต๊ฐ„ ์ „์ฒด๋ฅผ ์ฐจ์ง€ํ•˜๊ฒŒ ํ•˜๋Š” ๊ณต๊ฐ„
                  child: StreamBuilder<List<Schedule>>(  // ์ŠคํŠธ๋ฆผ๋นŒ๋” : ๋ฐ”๋€”๋•Œ๋งˆ๋‹ค ์•Œ๋ ค์คŒ
                    //์ŠคํŠธ๋ฆผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ
                    // ์ŠคํŠธ๋ฆผ: ๊ฐ’ ๋ถ„ํ•ดํ•œ ๊ฒƒ์„ ..
                    stream: GetIt.I<LocalDatabase>().watchSchedules(selectedDate),
                    // ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์—์„œ ๊ฐ€
                    // ์ƒˆ๋กœ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ์ˆ˜์ • ๋˜๋ฉด ์•Œ๋ ค๋‹ฌ๋ผ๊ณ  ํ•จ
                    builder: (context, snapshot){
                      // snapshot : ๋ฐ์ดํ„ฐ ์™€์“ดใ„ด์ง€, ์—๋Ÿฌ ์–ด๋–ค๊ฑด์ง€
                      if (!snapshot.hasData) {
                        return Container(); // ์—†์œผ๋ฉด : ๋นˆํ™”๋ฉด
                      }
                      return ListView.builder(
                        itemCount:snapshot.data!.length,
                        itemBuilder: (context, index){
                          final schedule = snapshot.data![index]; // ์ผ์ • ๋ชฉ๋ก์ด ๋“ค์–ด์˜ด
                          return Dismissible(
                            key:ObjectKey(schedule.id), //์œ ๋‹ˆํฌํ•œ ํ‚ค๊ฐ’
                            direction:DismissDirection.startToEnd,
                            onDismissed: (DismissDirection direction) {
                            GetIt.I<LocalDatabase>()
                                .removeSchedule(schedule.id);
                            },
                            child: Padding(
                              padding: const EdgeInsets.only(bottom: 8, left:8,
                                  right: 8),
                              child: ScheduleCard(
                                  startTime: schedule.startTime,
                                  endTime: schedule.endTime,
                                  content: schedule.content
                              ),

                            ),
                          );
                        },
                      );
                    },
                  )
              )
            ],
          ),
      ),
    );
  }

  void onDaySelected(DateTime selectedDate, DateTime focusedDate) {
    // ๋‚ ์งœ ์„ ํƒ๋  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰ํ•  ํ•จ์ˆ˜
    setState(() {
      this.selectedDate = selectedDate;
    });

  }
}

=> ๋‚ ์งœ ์„ ํƒ, ์ผ์ • ์ถ”๊ฐ€,์‚ญ์ œ,๋ชฉ๋กํ‘œ์‹œ ๋‹ด๋‹นํ•˜๋Š” ์•ฑ์˜ ๋ฉ”์ธ ํ™”๋ฉด

์ฝ”๋“œ ๋ถ„์„

/screen/home_screen.dart

return Dismissible(
                            key:ObjectKey(schedule.id), //์œ ๋‹ˆํฌํ•œ ํ‚ค๊ฐ’
                            direction:DismissDirection.startToEnd,
                            onDismissed: (DismissDirection direction) {
                            GetIt.I<LocalDatabase>()
                                .removeSchedule(schedule.id);
                            },
                            child: Padding(
                              padding: const EdgeInsets.only(bottom: 8, left:8,
                                  right: 8),
                              child: ScheduleCard(
                                  startTime: schedule.startTime,
                                  endTime: schedule.endTime,
                                  content: schedule.content
                              ),

                            ),
                          );
                          
GetIt.I<LocalDatabase>()
    .removeSchedule(schedule.id); //์‹ค์ œ ์‚ญ์ œ ๊ธฐ๋Šฅ schedule.id ๊ณ ์œ ํ•œ ๋ฒˆํ˜ธ
},

StreamBuilder<List<Schedule>>(
                stream: GetIt.I<LocalDatabase>().watchSchedules(selectedDate),
                builder: (context, snapshoot){
                  return TodayBanner(
                      selectedDate: selectedDate,
                      count: snapshoot.data?.length ?? 0,
                  );
                },
              ),
์ฝ”๋“œ์—ญํ• ๋น„๊ณ 
Dismissible์ผ์ • ์‚ญ์ œ์ผ์ •์„ ์™ผ์ชฝ์—์„œ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๋ฐ€์—ˆ์„ ๋•Œ ์ผ์ • ์‚ญ์ œ
direction:DismissDirection.startToEnd์ผ์ • ์‚ญ์ œ๋ฏธ๋Š” ๋ฐฉํ–ฅ ์ง€์ •
removeSchedule(schedule.id)์‚ญ์ œ ์กฐ๊ฑดDB์—์„œ ์ฃผ๋Š” ๊ณ ์œ ํ•œ ๋ฒˆํ˜ธ๋ฅผ ์ง€์ •ํ•˜์—ฌ ํ•ด๋‹น ๋ฒˆํ˜ธ ์ผ์ • ์‚ญ์ œ
count: snapshoot.data?.length ?? 0์ผ์ • ๊ฐœ์ˆ˜ ์„ธ๊ธฐDB์—์„œ ๊ฐ€์ ธ์˜จ ์ผ์ • ๋ชฉ๋ก์ด null์ด ์•„๋‹ˆ๋ฉด ๊ฐœ์ˆ˜ ๊ฐ€์ ธ์˜ด, null์ด๋ฉด 0์œผ๋กœ ํ‘œ์‹œ

์ฝ”๋“œ ๋™์ž‘ ์ˆœ์„œ

  1. selectedDate๋ฅผ ์˜ค๋Š˜ ๋‚ ์งœ๋กœ ์ดˆ๊ธฐํ™”
  2. ํ™”๋ฉด ๋ Œ๋”๋ง(ํ™”๋ฉด ๊ทธ๋ฆฌ๊ธฐ)
  3. ๋‹ฌ๋ ฅ์—์„œ ๋‚ ์งœ ์„ ํƒ
  4. onDaySelected ์‹คํ–‰ โ†’ setState๋กœ selectedDate ์—…๋ฐ์ดํŠธ
    โ†’ MainCalendar ์„ ํƒ ๋‚ ์งœ ํŒŒ๋ž€์ƒ‰์œผ๋กœ
    โ†’ TodayBanner ๋‚ ์งœ ๋ฐ”๋€œ
    โ†’ StreamBuilder๊ฐ€ ์ƒˆ ๋‚ ์งœ ์ผ์ • ์ž๋™์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ด
  5. ์ผ์ • ์ถ”๊ฐ€ ๋ฒ„ํŠผ ๋ˆ„๋ฆ„
    ScheduleBottomSheet ์˜ฌ๋ผ์˜ด
    โ†’ ์‹œ์ž‘์‹œ๊ฐ„/์ข…๋ฃŒ์‹œ๊ฐ„/๋‚ด์šฉ ์ž…๋ ฅ
    โ†’ ์ €์žฅ ๋ฒ„ํŠผ ํด๋ฆญ โ†’ DB์— ์ €์žฅ
    โ†’ StreamBuilder ๋ณ€ํ™” ๊ฐ์ง€
    โ†’ ScheduleCard ๋ชฉ๋ก์— ์ž๋™ ์ถ”๊ฐ€
    โ†’ TodayBanner ๊ฐœ์ˆ˜ +1 ์นด์šดํŠธ
  6. ์ผ์ • ์‚ญ์ œ
    ScheduleCard ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๋ฐ€๊ธฐ
    โ†’ onDismissed ์‹คํ–‰
    โ†’ removeSchedule(schedule.id)๋กœ DB์—์„œ ์‚ญ์ œ
    โ†’ StreamBuilder ๋ณ€ํ™” ๊ฐ์ง€
    โ†’ ๋ชฉ๋ก์—์„œ ์ž๋™์œผ๋กœ ์‚ฌ๋ผ์ง
    โ†’ TodayBanner ๊ฐœ์ˆ˜ -1 ์ค„์Œ

์ฝ”๋“œ ์ง์ ‘ ๋ณ€๊ฒฝ ํ›„ ์ ์šฉ

direction:DismissDirection.endToStart,

๋ฐฉํ–ฅ์„ startToEnd์—์„œ endToStart, up, down์œผ๋กœ ๋ฐ”๊ฟ”์„œ ์‚ญ์ œํ•˜๊ธฐ ์œ„ํ•ด ์ผ์ • ์นด๋“œ๋ฅผ ๋ฏธ๋Š” ๋ฐฉํ–ฅ์„ ๋ฐ”๊ฟ”๋ดค๋‹ค.

์ฝ”๋“œ๋ฏธ๋Š” ๋ฐฉํ–ฅ
startToEnd์™ผ์ชฝ -> ์˜ค๋ฅธ์ชฝ
endToStart์˜ค๋ฅธ์ชฝ -> ์™ผ์ชฝ
up์•„๋ž˜ -> ์œ„
down์œ„ -> ์•„๋ž˜

์—๋Ÿฌ & ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๐Ÿšจ์—๋Ÿฌ๐Ÿšจ : โŒ

์ƒˆ๋กœ ์•Œ๊ฒŒ๋œ ๊ฒƒ

StreamBuilder ๊ฐœ๋…์„ ์ƒˆ๋กœ ๋ฐฐ์› ๋‹ค. StreamBuilder๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๊ณ„์† ๋ณ€๊ฒฝ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•œ๋‹ค. Stream์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์˜ฌ ๋•Œ๋งˆ๋‹ค builder๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. snapshot์€ ๋“ค์–ด์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€ StreamBuilder๊ฐ€ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ค๋ฉด ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋กœ ๊ฐฑ์‹ ํ•˜์—ฌ ๊ฐ€์ง€๋กœ ์žˆ๋Š”๋‹ค. snapshot์€ ๋ฐ์ดํ„ฐ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ฐ์ดํ„ฐ ์ƒํƒœ๋„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋‹ค. (hasError, hasData, data)

๊ฒฐ๊ณผ๋ฌผ

์ผ์ • ์‚ญ์ œ

์ผ์ • ๊ฐœ์ˆ˜ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋กœ ๋ฐ˜์˜

0๊ฐœ์˜ ๋Œ“๊ธ€