티스토리 뷰

 

Flutter에서 앱을 개발하다 보면 Widget의 라이프사이클을 이해하는 것이 매우 중요하다.

 

Flutter의 Widget은 UI를 구성하는 기본 단위로, Stateful WidgetStateless Widget으로 나뉘는데

각 Widget은 생성, 업데이트, 제거 등의 과정을 거치며, 이 과정을 라이프사이클이라고 부른다.


1. Stateless Widget Lifecycle

Stateless Widget은 상태를 가지지 않는 위젯으로,

데이터가 변하지 않는 정적인 UI를 렌더링할 때 사용된다.

  • Create: Widget이 처음 생성될 때 호출
  • build(): UI를 빌드하여 화면에 표시
  • dispose: Widget이 화면에서 제거될 때 호출
Stateless Widget
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('Building MyHomePage'); // build() 호출 시 로그 출력
    return Scaffold(
      appBar: AppBar(title: Text('Stateless Widget Example')),
      body: Center(child: Text('Hello, Stateless World!')),
    );
  }

  // dispose는 Stateless Widget에서는 직접 호출되지 않음
  @override
  void dispose() {
    print('Disposing MyHomePage');
    super.dispose();
  }
}

 

Stateless Widgetbuild() 메서드만 주로 사용되며, 상태 변화가 없으므로 dispose는 명시적으로 호출되지 않는다. 위 코드에서 build()가 호출될 때마다 UI가 갱신된다.


2. Stateful Widget Lifecycle

Stateful Widget은 상태를 가지는 위젯으로, 사용자 입력이나 데이터 변화에 따라 UI가 동적으로 업데이트된다.

  1. Create Widget: Widget이 처음 생성될 때 호출
  2. Create State: State 객체 초기화. 여기서 initState()가 호출되어 초기 상태를 설정
  3. initState(): Widget이 화면에 추가될 때 한 번만 호출, 초기화 수행
  4. build(): UI를 빌드하여 화면에 표시. 상태가 변경될 때마다 다시 호출
  5. setState() / didUpdateWidget(): 상태가 변경되거나 위젯이 업데이트될 때 호출. setState() 통해 UI 갱신 가능
  6. deactivate: Widget이 비활성화될 때 호출(예: 화면에서 일시적으로 제거)
  7. dispose(): Widget이 완전히 제거될 때 호출, 리소스 해제
Stateful Widget
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    print('initState: Initializing state');
  }

  @override
  void didUpdateWidget(covariant MyHomePage oldWidget) {
    super.didUpdateWidget(oldWidget);
    print('didUpdateWidget: Widget updated');
  }

  @override
  void deactivate() {
    super.deactivate();
    print('deactivate: Widget is being deactivated');
  }

  @override
  void dispose() {
    super.dispose();
    print('dispose: Widget is disposed');
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
      print('setState: Counter incremented to $_counter');
    });
  }

  @override
  Widget build(BuildContext context) {
    print('build: Rebuilding UI');
    return Scaffold(
      appBar: AppBar(title: Text('Stateful Widget Example')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            Text('$_counter', style: TextStyle(fontSize: 24)),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

 

  • initState()는 위젯이 처음 생성될 때 호출되어 초기 상태(_counter = 0)를 설정한다.
  • build()는 UI를 렌더링하며, setState()가 호출될 때마다 다시 실행된다.
  • didUpdateWidget()은 위젯의 속성이 변경될 때 호출되며, 상태 갱신 로직을 추가할 수 있다.
  • deactivatedispose는 위젯이 화면에서 제거될 때 리소스를 정리하는 데 사용된다.

3. Stateful vs Stateless: 사용하는 경우

  • Stateless Widget: UI가 고정되어 있고 상태 변화가 필요 없는 경우(예: 텍스트, 이미지) 사용.
  • Stateful Widget: 사용자 입력이나 데이터 변화에 따라 UI가 동적으로 변해야 하는 경우(예: 카운터, 폼) 사용.

결론

모바일은 웹보다 상대적으로 작은 화면이라서 잦은 상태관리를 필요로 한다..

이렇게 복잡한 상태관리를 위해 Riverpod, Flutter Hooks, Bloc, GetX 등 여러 패키지가 존재하지만

최근에 많은 의존성 때문에 빌드 에러를 자주 겪어서 그런지 요즘은 되도록이면 바닐라한 코드(?)로 작성하려고 한다...

어차피 많은 패키지들도 결국은 기본 코드에서 시작하는 거고, 플러터가 업데이트 될 수록 같이 업데이트 해줘야 하는 의존성이 늘어나는 거니까... 기본에 충실하자 😢