플러터의 Stateful 위젯의 생명 주기

윤뿔소·2023년 6월 1일
1

Dart / Flutter

목록 보기
17/18

항상 변동되는 위젯, 컴포넌트 등은 수명이 있다. 리액트, 뷰 등에서도 유명한 Lifecycle에 대해서 알아보자.

막 암기해야한다고 혈안이 되지 말고 이정도는 이해하고 넘어가야 상태 변경을 통한 렌더링도 잘 하고, 어떠한 상태 관련 오류도 잘 잡아낼 수 있을 것이다. 가볍게 보고 넘어가자.

생명 주기

나는 리액트를 거쳐서 플러터도 배우고 있는데 생명 주기가 또 나왔다. 이때 의문점이 들었다.

생명 주기란 건 정확히 뭘까?? 왜 있을까??

그래서 일단 생명 주기에 대한 전략에 대해서 간략하게 정리하고 넘어가겠다.

여러 구글링을 해보고 내린 결론은 UI 요소의 생성, 갱신, 소멸 등의 과정을 관리하기 위해 존재하는 전략이다.

라이프사이클의 전략을 사용하는 다양한 이유들을 함축적으로 표현해보겠다 :

1. 초기화와 정리

라이프사이클 메소드를 통해 애플리케이션 또는 컴포넌트(위젯)의 초기화 및 정리 작업을 수행할 수 있다.
초기화 단계에서는 필요한 데이터를 로드하거나 리소스를 할당하고, 정리 단계에서는 사용한 리소스를 해제하거나 연결을 닫을 수 있다.

2. 상태 감지와 업데이트

라이프사이클 메소드를 통해 상태의 변화를 감지하고 이에 따라 적절한 업데이트 작업을 수행할 수 있다.
이를 통해 UI를 동적으로 변경하거나 상태에 따라 다른 동작을 유연하게 수행할 수 있다.

3. 외부 리소스 관리

라이프사이클 메소드를 사용하여 외부 리소스와의 상호작용을 관리할 수 있다.
예) DB 연결, API 요청, 파일 시스템 접근, 소셜 미디어 인증, 결제 서비스 연동 등을 메소드로 처리 가능

4. 오류 처리와 예외 상황 대응

라이프사이클 메소드를 활용해 예외 상황이나 오류가 발생했을 때 적절한 조치를 취할 수 있다.
예) 오류 로깅, 경고 메시지 표시, 예외 처리 등을 라이프사이클 메소드 내에서 처리 가능.

5. 성능 최적화

라이프사이클을 이용하여 성능 최적화를 수행할 수 있습니다. 마치 가비지 컬렉션.
필요한 경우에만 리소스를 로드하거나 업데이트를 수행하는 등 불필요한 작업을 피할 수 있다.

플러터의 생명 주기

생명 주기에 대해서 알아보았으니 이제 플러터의 생명 주기도 Araboza!

출처 : [flutter] 플러터 StatefulWidget 라이프 사이클 (lifecycle)

위젯 구축

  1. createState()
    State 객체를 생성하는 메소드. 이 메소드는 Stateful 위젯이 처음으로 생성될 때 호출.
  2. initState()
    State 객체가 생성된 후 최초로 호출되는 메소드. 초기화 작업을 수행하기에 적합한 시점. 예를 들어, 데이터를 가져오거나 초기 상태를 설정하는 등의 작업을 여기서 수행할 수 있음.
  3. didChangeDependencies()
    의존성이 변경된 경우 호출. State 객체가 의존하는 다른 객체나 데이터가 변경되었을 때 호출되는 메소드.

재 드로잉

  1. build()
    위젯을 빌드하는 메소드. 이 메소드는 위젯을 구성하고 레이아웃을 정의하는 역할을 함. build() 메소드는 초기 빌드 이후에도 여러 번 호출될 수 있음.
  2. didUpdateWidget()
    위젯이 업데이트되었을 때 호출. 이전의 위젯과 새로운 위젯을 비교하여 변경 사항을 처리할 수 있는 메소드.
  3. setState()
    상태가 업데이트되었을 때 호출되는 메소드. 상태를 업데이트하고 다시 렌더링할 필요가 있는 경우에 사용됨.

위젯 파기

  1. deactivate()
    위젯이 트리에서 제거되기 전에 호출. 상태를 저장하거나 해제하는 등의 작업을 수행할 수 있음.
  2. dispose()
    위젯이 영구적으로 제거될 때 호출. 메모리 누수를 방지하기 위해 리소스 해제 또는 정리 작업을 수행할 수 있음.

예시

이론은 다 봤으니 이제 예시로 넘어가겠다.

사실 플러터 생명주기 메소드는 그다지 명시적으로, 많이 사용되지는 않는다. 그 흐름을 파악할 뿐.

그나마 좀 사용하고 많이 사용하는 메소드 2가지를 보여주겠다.

import 'package:flutter/material.dart';

void main() {
  runApp(const App());
}

class App extends StatefulWidget {
  const App({super.key});

  
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  int _counter = 0;

  void onClickedPlus() {
    setState(() {
      _counter++;
    });
  }

  void onClickedMinus() {
    setState(() {
      _counter--;
    });
  }

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: const Color(0xFFF4EDDB),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                'Click Here!',
                style: TextStyle(fontSize: 30),
              ),
              Text(
                '$_counter',
                style: const TextStyle(fontSize: 30),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  IconButton(
                    iconSize: 40,
                    onPressed: onClickedPlus,
                    icon: const Icon(Icons.add_circle_outlined),
                  ),
                  IconButton(
                    iconSize: 40,
                    onPressed: onClickedMinus,
                    icon: const Icon(Icons.remove_circle),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

전에 만들었던 클릭하면 오르는 앱을 그대로 받아 예시를 쓰며 설명하겠다.

initState()

이제 여기에 초기화를 먼저 해주는 initState()를 호출해보자.

유의할 점은 컨텍스트 값이 설정되는 build 코드 전에 작성해야하고, 항상 한번만 호출된다.

사실 로컬 변수를 쓸 때 안써줘도 되는 부분이다. 또 그다지 사용을 많이 안할 거다.

class _AppState extends State<App> {
  int _counter = 0;

  
  void initState() {
    super.initState();
    _counter = 0;
  }

  void onClickedPlus() {
    setState(() {
      _counter++;
    });
  }
  
  ...

이렇게 변수를 초기화 해준다. 위 코드처럼 int _counter = 0;가 그냥 초기화 코드이기 때문에 따로 안써줘도 된다.

그럼 언제 사용하느냐. 컨텍스트에서 가져온 부모 요소에 의존하는 데이터나 아니면 API 통신을 통한 데이터를 감시, 감지할 때 많이 사용한다.

import 'package:flutter/material.dart';

class MyWidget extends StatefulWidget {
  
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  String data = '';

  
  void initState() {
    super.initState();
    // 데이터 초기화
    data = 'Initial data';

    // API 감지 및 초기화
    subscibeUpdatesAPI();
  }

  void subscribeToAPIUpdates() {
    // API 업데이트를 구독하는 로직
    // 업데이트가 발생하면 setState()를 사용하여 데이터를 갱신하고 UI를 업데이트
    // 이 예시에서는 간단히 data 값을 업데이트하는 것으로 가정
    // 실제로는 데이터를 가져오거나 처리하는 비동기 작업을 수행 코드 작성
    data = 'Updated data';

    setState(() {});
  }

  
  Widget build(BuildContext context) {
    return Container(
      child: Text(data),
    );
  }
}

뭐 이런 식으로 쓸 때 initState가 요긴하게 쓰인다. 이해는 안가더라도 '이렇게 쓰는구나' 하고 넘어가자

dispose()

dispose() 메소드는 자주 쓰여서 얘는 잘 알고 넘어가야한다.

위젯 제거 전 상태를 정리하고 싶을 때 사용하는 메소드.

  1. 리소스 해제
    위젯이 사용한 리소스(예: 메모리, 파일 핸들, 네트워크 연결)를 해제해야 할 때 dispose() 메소드를 활용.
  2. 구독 해제: initState()에서 등록한 구독이나 이벤트 리스너를 dispose() 메소드에서 해제하여 메모리 누수를 방지하고 불필요한 업데이트를 막을 수 있음.

어떤 API 업데이트나 이벤트 리스너로부터 구독 - 감지를 취소하거나 form의 리스너로부터 벗어나고 싶을 때 사용할 수 있다.

class MyWidget extends StatefulWidget {
  
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  StreamSubscription _subscription;

  
  void initState() {
    super.initState();
    // 예시로 StreamSubscription을 등록
    _subscription = myStream.listen((data) {
      // 데이터 처리 로직
    });
  }

  
  void dispose() {
    // 리소스 해제 및 구독 해제
    _subscription.cancel();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    // 위젯의 빌드 로직
    return Container();
  }
}

이런 식으로 쓰면 된다. 그냥 나중에 만들면서 이해하면 된다.

정리

class _AppState extends State<App> {
  
  void initState() {
    super.initState();
    _counter = 0;
    print('init!');
  }

  
  void dispose() {
    super.dispose();
    print('dispose!');
  }

  
  Widget build(BuildContext context) {
    print("build!");
    return MaterialApp(
    ...

각 메소드를 설정하고 print해보면 저렇게 라이프사이클이 돌면서 출력된다.

짠! 됐다! 이것만 봐도 라이프 사이클이 어떻게 도는지 이해가 갈 것이다. 끝!

profile
코뿔소처럼 저돌적으로

6개의 댓글

comment-user-thumbnail
2023년 6월 3일

고생하셨습니다.

답글 달기
comment-user-thumbnail
2023년 6월 3일

언어마다 생명주기는 필수 학습인 것 같네요 ㅋㅋ 잘 보고 갑니당

답글 달기
comment-user-thumbnail
2023년 6월 4일

다른 것 같으면서도 비슷하네용 ㅎㅎ 고생하셨습니당 !!

답글 달기
comment-user-thumbnail
2023년 6월 4일

플러터도 생명주기가 있군요! 잘 읽었습니다 👍👍

답글 달기
comment-user-thumbnail
2023년 6월 4일

정말 처음 배우는 언어는 생명주기 학습은 꼭거쳐가는 과정인 것 같아요 ㅎㅎ 정리 잘 해주셔서 잘 보고 갑니당 !

답글 달기
comment-user-thumbnail
2023년 6월 4일

생명주기! 잘 보고 갑니당 비교하며 보게 되네요

답글 달기