[Flutter] TableCalendar markerBuilder delay 해결

Ohgyuchan·2022년 5월 8일
0

Flutter

목록 보기
11/25
post-thumbnail

Before

체크를 하고, 화면을 한 번 더 눌러야지 반영

After

체크를 하고, 화면을 다시 터치하지 않아도 200ms 후 바로 반영

투두리스트 어플을 만드는데 어느 순간부터 체크하고 나면 달력 UI에 markerBuilder반영이 늦게 되는 현상이 발생했다.

처음에는 readGrass 함수에 setState를 넣어주면 괜찮을 거라고 생각했는데, FutureBuilder에서 readGrass를 읽어와서 그런지 setState() or markNeedsBuild called during build 에러가 떴다.
그래서 Todo의 check를 update할 때 delay를 좀 주고 그 후에 바로 다시 읽어오게 했더니 고쳐졌다.

Solution Code

readGrass

Firestore에서 TodoList Collection에 있는 DocumentSnapshotList를 TodoModel 클래스 타입으로 데이터를 읽어와서 날짜에 맞게 kEvents에 추가 읽어 오는 코드

kEvents

LinkedHashMap<DateTime, List<TodoModel>> kEvents =
      LinkedHashMap<DateTime, List<TodoModel>>(
    equals: isSameDay,
    hashCode: getHashCode,
  );

readGrass

readGrass(List<DocumentSnapshot> snapshot) {
    kEvents.addAll({
      for (var item in snapshot)
        DateTime.parse(item['eventDay'].toDate().toString()):
            List.generate(item['todoEvents'].length, (index) {
          return TodoModel(
              key: item['todoEvents'][index]['key'],
              todoName: item['todoEvents'][index]['todoName'],
              groupName: item['todoEvents'][index]['groupName'],
              selectedTime: item['todoEvents'][index]['selectedTime'],
              controller: TextEditingController(
                  text: item['todoEvents'][index]['todoName']),
              longPressed: false,
              addedDate: item['todoEvents'][index]['addedDate'] ?? [],
              checked: item['todoEvents'][index]['checked']);
        })
    });
  }

grassDocList는 GetxController에서 bindStream으로 꽂아둔 상태이다.

updateGrass

투두를 체크했을 때 Firestore에 업데이트 하고 나서 delay를 200ms 정도 주고, 바로 다시 읽어오게 했더니 잘 된다.

updateGrass() async {
    if (kDebugMode) {
      print('update firestore document');
    }
    var time = kSelectedDay;

    userCollection
        .doc(userController.currentUserUid)
        .collection('todoList')
        .doc(convertToLocalTimeString(time))
        .set(
      {
        'eventDay': time,
        'todoEvents': FieldValue.arrayUnion(
          toMapList(selectedEvents),
        ),
      },
    ).catchError((e) {
      if (kDebugMode) {
        print(e.toString());
      }
    });
    await Future.delayed(const Duration(milliseconds: 200));
    setState(() {
      kEvents.addAll({
        for (var item in todoController.grassDocList)
          DateTime.parse(item['eventDay'].toDate().toString()):
              List.generate(item['todoEvents'].length, (index) {
            return TodoModel(
                key: item['todoEvents'][index]['key'],
                todoName: item['todoEvents'][index]['todoName'],
                groupName: item['todoEvents'][index]['groupName'],
                selectedTime: item['todoEvents'][index]['selectedTime'],
                controller: TextEditingController(
                    text: item['todoEvents'][index]['todoName']),
                longPressed: false,
                addedDate: item['todoEvents'][index]['addedDate'] ?? [],
                checked: item['todoEvents'][index]['checked']);
          })
      });
    });
  }

FutureBuilder로 readGrass가 있지만 updateGrass에서도 update하자마자 바로 다시 읽어와야지 UI에 제대로 반영이 된다.

FutureBuilder todoEventList() {
    return FutureBuilder(
      future: readGrass(todoController.grassDocList),
      builder: ((context, snapshot) {
        return ListView.builder(
          shrinkWrap: true,
          physics: const NeverScrollableScrollPhysics(),
          itemCount: selectedEvents.length,
          itemBuilder: (context, index) {
            return todoEventListTile(selectedEvents, index);
          },
        );
      }),
    );
  }

markerBuilder code

check된 TodoModel 갯수에 따라서 marker생성

markerBuilder: (context, date, List<TodoModel> events) {
                  int eventLength =
                      events.where((element) => element.checked == true).length;
                  if (eventLength > 0 && events.isNotEmpty) {
                    return Center(
                      child: Stack(
                        alignment: Alignment.center,
                        children: [
                          SvgPicture.asset(
                            'assets/check.svg',
                            color: AppColor.primary[
                                eventLength > 3 ? 600 : eventLength * 200],
                            width: 28,
                            height: 22,
                          ),
                          Text(
                            '${date.day}',
                            style: convertToLocalTimeString(date) ==
                                    convertToLocalTimeString(kSelectedDay)
                                ? const TextStyle(
                                    color: AppColor.white,
                                    fontWeight: FontWeight.bold,
                                  )
                                : const TextStyle(
                                    color: AppColor.white,
                                  ),
                          ),
                        ],
                      ),
                    );
                  }
                  return const SizedBox();
                },

고치긴 했지만, 추후 보완이 더 필요한 것 같다.
이상하게 TodoModel.fromSnapshot 이나 TodoModel.fromMap 등으로 읽어오면 더 느린 것 같아서, 원래는 저 두 가지를 사용했었는데 지금은 그냥 기본 constructor를 사용하는 것으로 바꿨다.

원래 GetX를 사용하면 stateless위젯을 사용하는 게 적성인데, 달력은 상태변화가 동시에 다수가 필요해서 이 화면에서만 stateful을 사용했다.

개인적인 생각이지만 GetX를 쓴다고 해서 너무 stateless에 집착하지 않는 게 더 나은 것 같다. 아님말고

오렌지데이 다운로드 링크: ios android

profile
Flutter 개발자

0개의 댓글