Java / Spring

Flutter - dispose() 본문

Front

Flutter - dispose()

밍구밍구밍 2025. 8. 9. 14:00

dispose() 가 필요한 이유:

- 입력 값에 대한 변수를 생성할 때 TextEditingController() 와 같은 함수를 호출해서 입력할 변수 데이터에 초기화 시키는 경우

- TextEditingController 는 단순한 값 저장 변수가 아니라 Stream 내부에서 키 입력 이벤트를 구독하는 리스너와 같은 리소스를 사용한다. StatefulWidget 이 화면에서 사라져도 이 컨트롤러가 계속 메모리에 남아 있으면, 사용하지 않는 리소스를 계속 점유하게 되어 메모리 누수나 예상치 못한 동작이 생길 수 있기 때문에 dispose() 를 통해 내부 리소스를 해제해 줘야 한다.

 

dispose() 를 명시적으로 오버라이드 해야 하는 경우

1. Controller 관련 이벤트 선언 시

  • TextEditingController
  • ScrollController
  • TabController
  • PageController
  • AnimationController

2. Stream 관련 이벤트 선언 시

  • StreamSubscription
  • BLoC, RxDart, WebSocket 등에서 사용하는 구독

3. Timer / 주기 작업

  • Timer
  • Timer.periodic
  • Ticker

4. Focus / Node 관련

  • FocusNode
  • FocusScopeNode

5. 기타 수동 해제 리소스

  • 데이터베이스 연결 객체 (ex. sqflite의 Database 인스턴스)
  • Platform channel에서 생성한 네이티브 리소스
  • 지도/카메라 컨트롤러 (GoogleMapController, CameraController 등)
  • 비디오/오디오 플레이어 컨트롤러

dispose() 를 쓰지 않아도 되는 경우

  • 단순 변수(String, int, bool 등)
  • 단순 모델 객체 (MyModel, DTO 등, GC가 알아서 정리)
  • const 위젯 / 값
  • 네트워크 요청 후 응답 받고 끝나는 API 호출(단, 스트림형 API는 예외)

dispose() 동작 방식

https://docs.flutter.dev/cookbook/forms/text-field-changes << 공식문서에서도  dispose() 권장

 

- dispose() 는 플러터에서 State 객체가 더 이상 필요 없을 때 자동으로 호출 되므로, 이 타이밍에 dispose() 를 불러서 내부 리소스를 해제한다.

 

dispose() 를 사용하지 않으면 발생하는 문제

- 화면을 여러 번 갔다 왔다 하면, 이전 화면의 TextEditingController 가 계속 쌓임

- 입력 이벤트 리스너가 중복 실행될 수 있음

- 장기적으로 메모리 사용량이 불필요하게 증가

 

사용 예시) 아래와 같이 게시물을 등록하기 위한 Upload 라는 커스텀 위젯이 있다고 할 때

class Upload extends StatefulWidget {
  const Upload({super.key, required this.imageFile});
  final File imageFile;

  @override
  State<Upload> createState() => _UploadState();
}

class _UploadState extends State<Upload> {
  ...
}

 

class _UploadState extends State<Upload> {

  final TextEditingController _contentController = TextEditingController();
  final TextEditingController _userController = TextEditingController();
  final formattedDate = DateFormat('MMM d').format(DateTime.now());

(* State 내부에 업로드 시 입력할 TextController() 를 선언, 위젯이 초기화 될 때 해당 변수도 함께 메모리에 추가 된다.)

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('이미지 업로드')),
    body: Column(
      children: [
        Image.file(widget.imageFile),
        const SizedBox(height: 16),
        TextField(
          controller: _contentController,
          decoration: const InputDecoration(
            labelText: '내용을 입력해주세요',
            border: OutlineInputBorder(),
          ),
        ),
        TextField(
          controller: _userController,
          decoration: const InputDecoration(
            labelText: '이름을 입력해주세요',
            border: OutlineInputBorder(),
          ),
        ),
        ElevatedButton(
          onPressed: () {
            final newPost = {
              "id": DateTime.now().millisecondsSinceEpoch,
              "image": widget.imageFile.path,
              "likes": 0,
              "date": formattedDate,
              "content": _contentController.text,
              "liked": false,
              "user": _userController.text
            };
            Navigator.pop(context, newPost);
          },
          child: const Text("저장"),
        ),
      ],
    ),
  );
}

 

그리고 위와 같이 Scaffold() 내부에 TextField() 에서 위젯이 랜더링 될때 초기화 시켜놓은 TextController() 의 변수를 입력하면,

이 변수가 newPost {} 내부에 content 와 user 라는 key 값에 value 로 초기화 된 뒤 사용자가 Text("저장") 을 누르면 Navigator.pop() 이 동작하면서 dispose() 가 실행된다.

 

(+ Navigator.pop() 은 코드에서 저장 버튼의 onPressed 콜백 내부에 있기 때문에 사용자가 "저장" 버튼을 눌렀을 때 바로 실행 됨)

 

* dispose() 선언 예시 

class Upload extends StatefulWidget {
  const Upload({super.key, required this.imageFile});
  final File imageFile;

  @override
  State<Upload> createState() => _UploadState();
}

class _UploadState extends State<Upload> {

  final TextEditingController _contentController = TextEditingController();
  final TextEditingController _userController = TextEditingController();
  final formattedDate = DateFormat('MMM d').format(DateTime.now());

  @override
  void dispose() {
    _contentController.dispose();
    _userController.dispose();
    super.dispose();
  }

 

동작 흐름

1. 사용자가 특정 Navigator.pust() 로 Upload 위젯을 랜더링 시킴

2. _UploadState 객체가 생성, initState() => build() 실행

3. TextEditingController 2개 (_user, _content) 가 메모리에 올라감

4. 사용자가 저장 버튼을 누르면 Navigator.pop() 이 동작한다. 이 때, 화면이 네이게이션 스택에서 제거 되고, 플러터프레임워크는 이 위젯의 State를 더 이상 사용할 필요가 없다고 판단하고 dispose() 를 이 시점에 자동으로 호출한다. 

'Front' 카테고리의 다른 글

Flutter - 많이 사용하는 명령어  (0) 2025.11.22
Flutter : {Super.key?}  (0) 2025.07.28
[VUE/Composition API] Axios + Ref()  (0) 2025.03.24
[Vue] 상태관리, State & mutaions  (0) 2025.02.20
[Vue] Axios  (0) 2025.01.06