Flutter 앱에서 게임 타입에 따라 NumberPad
와 ColorPad
위젯을 조건부로 렌더링하는 과정에서 특이한 문제가 발생했다. 카운트다운이 끝난 후 게임 패드가 뒤집히는 기능이 NumberPad
에서는 didUpdateWidget
만으로 정상 작동했지만, ColorPad
에서는 initState
에 추가 코드를 넣어야만 작동하는 것이다...
// GameScreen에서의 조건부 렌더링
isNumberType
? NumberPad(
state: state,
decreaseCount: (int value) => onAction(GameAction.decreaseCount(value)),
gameSuccess: () => onAction(const GameAction.gameSuccess()),
gameFail: () => onAction(const GameAction.gameFail()),
)
: ColorPad(
state: state,
decreaseCount: (int value) => onAction(GameAction.decreaseCount(value)),
gameSuccess: () => onAction(const GameAction.gameSuccess()),
gameFail: () => onAction(const GameAction.gameFail()),
)
Flutter는 위젯 트리를 효율적으로 관리하기 위해 Element 재사용 전략을 사용한다고 한다. 조건부 렌더링에서 위젯이 교체될 때, Flutter는 다음과 같은 과정으로 판단:
runtimeType
)의 위젯이 있는지 확인문제는 Key가 없는 조건부 렌더링에서 Flutter의 Element 재사용 전략이 예측 불가능하게 동작할 수 있다는 점이다. 이로 인해 ColorPad
가 새로 생성되는 경우가 발생했고, 이때는 didUpdateWidget
이 호출되지 않았다.
NumberPad
는 Element가 재사용되어 didUpdateWidget
이 호출되었지만, ColorPad
는 새 Element가 생성되어 didUpdateWidget
대신 initState
만 호출되는 상황이 발생.
가장 효과적인 해결책은 각 위젯에 고유한 Key를 부여하는 것:
isNumberType
? NumberPad(
key: const ValueKey('number_pad'),
state: state,
// ... 기타 매개변수
)
: ColorPad(
key: const ValueKey('color_pad'),
state: state,
// ... 기타 매개변수
)
ValueKey를 지정하면 Flutter가 위젯의 정체성을 명확히 인식하여 Element 재사용 패턴이 예측 가능해지고, didUpdateWidget
호출이 안정적으로 보장된다.
두 위젯 모두에 동일한 방어적 코드를 적용하여 어떤 상황에서도 일관된 동작을 보장할 수 있음:
void initState() {
super.initState();
// 기본 초기화...
// 생성 시점에 이미 카운트다운이 끝났다면 처리
if (widget.state.isCountDownFinished) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_flipAll();
});
}
}
void didUpdateWidget(covariant GamePad oldWidget) {
super.didUpdateWidget(oldWidget);
// 정상적인 카운트다운 완료 시 처리
if (!oldWidget.state.isCountDownFinished &&
widget.state.isCountDownFinished) {
_flipAll();
}
}
조건부 렌더링에서 서로 다른 위젯 타입을 교체할 때는 항상 명시적인 Key를 사용해야 한다. 이는 다음과 같은 이점을 제공한다:
didUpdateWidget
등의 호출 보장Flutter 앱 개발에서는 다양한 엣지 케이스를 고려한 방어적 코딩이 중요하다:
Flutter의 조건부 렌더링에서 발생하는 위젯 생명주기 문제는 명시적인 Key 사용과 방어적 코딩 패턴을 통해 해결할 수 있다. 특히 ValueKey를 사용하여 위젯의 정체성을 명확히 하는 것이 가장 효과적인 해결책이다.
이번 트러블 슈팅을 통해 Flutter의 위젯 생명주기와 Element 재사용 메커니즘에 대해 깊이 이해할 수 있었고, 더 안정적이고 예측 가능한 앱을 개발하는 데 도움이 되었다.