StatefulWidget의 build()에서 변수 값을 초기화해는 구문을 넣어주니 문제가 발생했다.
문제발생
class DailyCalendar extends StatefulWidget {
const DailyCalendar({Key? key, required this.onDayClick, required this.selectedDateTime}) : super(key: key);
final Function(DateTime) onDayClick;
final DateTime selectedDateTime;
// ..
@override
_DailyCalendarState createState() => _DailyCalendarState();
}
class _DailyCalendarState extends State<DailyCalendar> {
@override
Widget build(BuildContext context) {
DateTime selectedDateTime = widget.selectedDateTime;
return TableCalendar(
// ..
selectedDayPredicate: (dateTime) => isSameDay(dateTime, DateTime(_selectedDateTime.year, _selectedDateTime.month, _selectedDateTime.day)),
onDaySelected: (selectedDay, focusedDay) {
setState(() {
selectedDateTime = selectedDay;
});
widget.onDayClick.call(selectedDay);
},
);
}
}
위의 코드는 날짜를 클릭하면 색깔이 변경되며 날짜가 표시되고, 확인 버튼을 누르면 선택한 날짜 데이터가 콜백으로 전달되는 커스텀 달력이고 포스트 설명에서 필요없는 부분은 전부 빼고 주석처리 하였다.
어느날 리팩토링을 하고 실행해보니 날짜 선택이 되지 않아서 헤매던 중에 이유를 찾았다.
선택이 안되던 이유는 build()안으로 선택된 날짜에 대한 변수를 옮긴 것 때문이었다.
플러터에서 상태가 변함에 따라 위젯을 rebuild하는 방식은 안드로이드에서 생명주기를 다루던 방식과는 다르게 생각해야한다.
StatefulWidget
의 Lifecycle
은 setState를 호출하면 그려져있던 위젯을 지우고 build()를 재호출한다.
build()를 재호출했으므로 build()안에 넣어주었던 변수인 selectedDate가 widget.selectedDateTime으로 계속 초기화가 되었던 것이었다.
해결 방법
build()안에서 초기화해주던 변수들을 전부 State 클래스 멤버변수로 빼주었다.
그리고 밖에서 전달받은 데이터로 initState
안에서 초기화해준다.
이렇게하면 위젯이 최초로 생성되었을때 변수에 대한 초기화가 initState에서 한번만 이루어진다.
그 후, 멤버변수를 통해 클릭했을 때 값을 바꾸어주면 UI가 정상적으로 바뀌게 된다.
class DailyCalendar extends StatefulWidget {
const DailyCalendar({Key? key, required this.onDayClick, required this.selectedDateTime}) : super(key: key);
final Function(DateTime) onDayClick;
final DateTime selectedDateTime;
// ..
@override
_DailyCalendarState createState() => _DailyCalendarState();
}
class _DailyCalendarState extends State<DailyCalendar> {
late DateTime _selectedDateTime;
@override
void initState() {
super.initState();
_selectedDateTime = widget.selectedDateTime;
}
@override
Widget build(BuildContext context) {
return TableCalendar(
// ..
selectedDayPredicate: (dateTime) => isSameDay(dateTime, DateTime(_selectedDateTime.year, _selectedDateTime.month, _selectedDateTime.day)),
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDateTime = selectedDay;
});
widget.onDayClick.call(selectedDay);
},
);
}
}
정리
정리하자면 StatefulWidget의 build()안에서 변수 초기화구문을 넣어주면 build()가 실행될 때 마다 초기화 했을 때 넣어준 값으로 초기화가 되는 일이 발생하고 결국 값이 바뀌지 않게 된다.
그러므로, 초기화구문은 initState()에서 분리해서 사용해주어야 한다.