1. 소개
Flutter 앱 개발에서 위젯의 생명주기를 이해하는 것은 매우 중요합니다. 이는 앱의 성능을 최적화하고, 메모리 누수를 방지하며, 예기치 않은 오류를 피하는 데 핵심적인 역할을 합니다. 이 포스트에서는 실제 스톱워치 앱 개발 중 발생한 타이머 관련 오류를 통해 Flutter 위젯의 생명주기를 심도 있게 살펴보겠습니다.
2. Flutter 위젯 생명주기 개요
Flutter에는 두 가지 주요 위젯 유형이 있습니다: StatelessWidget과 StatefulWidget. StatefulWidget의 경우, 다음과 같은 주요 생명주기 메서드가 있습니다:
- initState(): 위젯이 생성될 때 호출됩니다.
- build(): 위젯을 렌더링할 때 호출됩니다.
- dispose(): 위젯이 제거될 때 호출됩니다.
3. 사례 연구: 스톱워치 앱의 타이머 오류
다음과 같은 스톱워치 기능을 구현한 SportsModeDetailScreen
위젯이 있었습니다:
class SportsModeDetailScreen extends StatefulWidget {
@override
State<SportsModeDetailScreen> createState() => _SportsModeDetailScreenState();
}
class _SportsModeDetailScreenState extends State<SportsModeDetailScreen> {
Stopwatch _stopwatch = Stopwatch();
Timer? _timer;
String _stopwatchText = '00:00:00';
bool _isRunning = false;
// ... (기타 메서드)
void _startStopWatch() {
setState(() {
_isRunning = true;
});
_stopwatch.start();
_timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
setState(() {
_stopwatchText = _getFormattedTime(_stopwatch.elapsedMilliseconds);
});
});
}
// ... (기타 메서드)
}
이 코드에서, 사용자가 다른 화면으로 이동할 때 다음과 같은 오류가 발생했습니다:
FlutterError (setState() called after dispose(): _SportsModeDetailScreenState#caba4(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build).
This error can occur when code calls setState() from a timer or an animation callback.
4. 문제의 원인
이 오류의 주요 원인은 두 가지입니다:
- 위젯이 dispose된 후에도 타이머가 계속 실행되고 있었습니다.
- 타이머 콜백에서 위젯이 이미 제거된 후에도
setState()
를 호출하려 했습니다.
5. Flutter 생명주기와 관련된 주의사항
setState()
는 위젯이 활성 상태일 때만 호출해야 합니다.- 타이머, 스트림 구독 등의 리소스는 위젯이 dispose될 때 반드시 정리해야 합니다.
6. 해결 방안
dispose()
메서드에서 타이머를 취소합니다.- 타이머 콜백에서
mounted
속성을 확인합니다. setState()
호출 전에 위젯이 여전히 활성 상태인지 확인합니다.
7. 개선된 코드 예시
class _SportsModeDetailScreenState extends State<SportsModeDetailScreen> {
Stopwatch _stopwatch = Stopwatch();
Timer? _timer;
String _stopwatchText = '00:00:00';
bool _isRunning = false;
@override
void dispose() {
_timer?.cancel(); // 타이머를 직접 취소
_stopwatch.stop(); // 스톱워치 중지
super.dispose();
}
void _startStopWatch() {
setState(() {
_isRunning = true;
});
_stopwatch.start();
_timer = Timer.periodic(Duration(seconds: 1), (Timer t) {
if (mounted) { // 위젯이 여전히 트리에 있는지 확인
setState(() {
_stopwatchText = _getFormattedTime(_stopwatch.elapsedMilliseconds);
});
} else {
t.cancel(); // 위젯이 제거되었다면 타이머 취소
}
});
}
// ... (기타 메서드)
}
8. 추가적인 생명주기 관리 팁
FutureBuilder
나StreamBuilder
를 사용하여 비동기 작업을 관리할 수 있습니다.- Provider, Riverpod 같은 상태관리 라이브러리를 사용하면 위젯의 생명주기와 상태 업데이트를 더 효과적으로 관리할 수 있습니다.
9. 결론
Flutter 위젯의 생명주기를 이해하고 적절히 관리하는 것은 견고하고 효율적인 앱을 개발하는 데 필수적입니다. 이 사례 연구를 통해 우리는 타이머 관련 오류를 해결하고, 더 나아가 Flutter의 위젯 생명주기에 대한 깊은 이해를 얻을 수 있었습니다.