[Flutter] LifeCycle(생명주기)

ryan·2021년 9월 3일
5

Dart/Flutter

목록 보기
20/21
post-thumbnail

참고 링크

State(상태)

State(상태)란 위젯에 대한 속성을 의미한다. 예를 들어서 Text위젯의 text를 상태라고 할 수 있고, style, fontWeight, color 등 해당 위젯이 가지고 있는 속성을 모두 상태라고 부른다. 이렇게 위젯은 모두 상태를 가지고 있고, 위젯은 위젯 트리에 의해서 관리되고 있다.

Stateless widget

Stateless widget도 State를 가지고, 한 번 생성되면 절대 바뀌지않는다. 즉, 위젯을 한 번만 그리고 다시 그리지 않는다. Stateless를 바꾸려면 완전히 파괴(destroy)하고, 다시 rebuild하는 방법 밖에 없다. 그래서 Stateless widget의 라이프 사이클은 매우 간단하다. 신경써야하는 부분은 build method 뿐이다. 즉, 매 번 Stateless widget이 build 될 때마다 build method가 호출되고, 이 build method 내에서 사용자가 원하는 내용의 stateless widget을 새롭게 생성하면 된다.

Stateful widget

Stateful widget은 상태가 변경되면 build를 여러 번 하는 위젯이다. Stateful widget은 State object와 결합하게 된다. State object의 역할은 위젯의 구성요소나 위젯의 속성들을 지속적으로 추적하는 것이기때문에 사용자가 원하는 입 맛대로 위젯의 구성요소나 속성에 관련된 변수들을 setState 메서드를 사용해서 언제라도 업데이트 할 수 있다. Stateful widget은 화면을 여러 번 그리기 때문에 Stateless widget보다 관리하기 어렵고, 성능도 조금 낮아질 수 있다. 하지만, UI가 변경된 상태에 따라 업데이트가 되기 때문에 사용자에게 더 나은 경험을 선사해 줄 수 있다.

Stateful widgets live longer than Stateless widgets
Stateful 위젯은 Stateless 위젯보다 더 긴 생명주기를 갖는다

Life Cycle(생명주기)

Flutter가 Stateful widget을 build 할 때, 처음으로 위젯의 constructor(생성자) function을 실행한다. 그리고나서, createState() method를 호출한다. Stateful widget에서는 constructor function이 처음으로 실행되는 것을 볼 수 있다. 반면에, Stateful widget의 State object에서는 State의 생명 주기가 createState() method가 호출될 때, 시작하는 것을 볼 수 있다.

Note: constructor function은 실행될 때, widget 속성이 비어있는 상태이기때문에 생명주기의 한 부분을 차지하지는 않는다.

1. createState()

createState() method는 state object(객체)를 생성한다. 이 object는 해당 widget에 대한 모든 변경 가능한 state가 유지되는 곳이다. 이 method는 StatefulWidget 내에서 필요하다.

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

mounted(true)

State object를 생성하면, 프레임워크는 mounted라는 boolean 속성을 true로 설정해서, State object를 BuildContext와 연결한다. 이 속성은 이 State object가 현재 위젯 트리에 있는지, 없는 지에 대한 정보를 제공한다.

Note: 이 단계는 생명 주기의 실제 단계로 표시되지않지만, background에서 진행 중인 작업을 아는 것이 중요하다.

2. initState()

object가 트리에 주입되면(mounted 속성은 true로 설정), initState()가 class constructor 다음으로 자동으로 실행된다. initState()는 state obejct가 처음 생성될 때, 한 번만 호출된다.

Note: initState()에는 BuildContext를 사용할 수 없다.

Tip: initState()를 HTTP request를 관리하고, 위젯의 데이터를 변경할 수 있는 stream 구독, 알림변경 또는 기타 다른 object를 핸들링하는데 사용하자.

Subscription is a connection between Subscriber and Publisher. Basically, Publisher will create a subscription for every Subscriber which will try to subscribe to it, and this subscription will handle requests from the subscriber. Publisher act as the storage of data and subscription will obtain data from it.
Subscription은 Subscriber와 Publisher 간의 연결이다. 기본적으로 Publisher는 Subscription을 시도할 모든 Subscriber에 대해 Subscription을 만들고 이 Subscription은 Subscriber의 요청을 처리한다. Publisher는 데이터 저장소 역할을 하고 구독은 데이터를 가져옵니다.

@override
void initState() {
  super.initState();
  // TODO: implement initState
}

3. didChangeDependencies()

프레임워크는 initState() 다음으로 didChangeDependencies()를 호출한다. didChangeDependencies()는 위젯이 변화에 의존하는 object일 때도 호출된다. build method는 항상didChangeDependencies() 다음에 호출된다. 그래서, didChangeDependencies()은 거의 필요하지 않다. 하지만, didChangeDependencies()는 BuildContext.inheritFromWidgetOfExactType을 호출할 수 있는 첫 번째 method다.

4. build()

build() method는 필수적이고, 생명 주기 동안 여러 번 호출되지만, 처음 호출되는 것은 didChangeDependencies() method가 호출된 다음이다. 따라서, state에 속한 위젯이 업데이트될 때마다 프레임워크는 항상 build() 메서드를 실행한다.(예, didUpdateWidget() 또는 setState() method가 호출될 때마다)

5. didUpdateWidget()

didUpdateWidget() method는 부모 위젯이 구성을 변경하고, 위젯을 다시 build해야하는 경우에 호출된다. 프레임워크는 이전 위젯을 새 위젯과 비교하는데 사용할 수 있는 argument를 준다. Flutter는 didUpdateWidget() 이후에 build() method를 호출한다.

Tip: 새 위젯을 이전 위젯과 비교해야 하는 경우에 didUpdateWidget() 사용하자.

@override
void didUpdateWidget(covariant MyHomePage oldWidget) {
  super.didUpdateWidget(oldWidget);
  // TODO: implement didUpdateWidget
}

setState()

setState() method는 자주 Flutter 프레임워크 자체와 개발자로부터 호출된다. setState() method는 현재 object 내부 상태가 "dirty"라는 것을 프레임워크에 알려준다. 즉, UI에 영향을 줄 수도 있는 방식으로 변경되었음을 의미한다. 이 알림 후에 프레임워크는 build() method를 호출해서 위젯을 업데이트하고 다시 build한다.

Tip: State object의 내부 상태를 변경할 때마다, setState() method에서 변경하자.

setState(() {
  // implement setState
});

Note: setState는 개발자가 호출하는 유일한 메서드이기 때문에 생명 주기 method의 단계로 표시하지않았다.

6. deactivate()

deactivate() method는 위젯 트리에서 위젯이 제거될 때 호출되지만, state가 위젯 트리의 한 지점에서 다른 지점으로 이동할 때, 현재 프레임 변경이 완료되기 전에 다시 주입될 수 있다. deactivate() method는 거의 사용되지 않는다.

@override
void deactivate() {
  super.deactivate();
  // TODO: implement deactivate
}

7. dispose()

dispose() method는 위젯 트리에서 state object가 영구적으로 제거될 때 호출된다.

Tip: data listeners 또는 life connections를 clean up하는 데에 dispose() method를 사용하자.

DataListener is an event listener interface that supports processing events produced by a corresponding DataProvider instance.
DataListener는 해당 DataProvider 인스턴스에서 생성된 이벤트 처리를 지원하는 이벤트 리스너 인터페이스다.

@override
void dispose() {
  super.dispose();
  // TODO: implement dispose
}

mounted(false)

dispose() method 다음에는 State object가 현재 트리에 없으므로 mounted 속성은 이제 false다. state object는 다시 mount할 수 없고, setState()가 호출되면 에러가 발생한다.

reassemble()

reassemble()는 hot reload를 실행할 때마다 호출된다. reassemble()이 호출된 이후에 위 그림처럼 생명주기가 차례대로 진행된다.


생명주기 예시 코드

아래는 생명주기 예시 코드로, 직접 사용해보면서 생명주기를 확인해보면 좋을 것 같다.

// main.dart
import 'package:flutter/material.dart';
import './ScreenC.dart';
import './ScreenB.dart';
import './ScreenA.dart';

int addNum(int a, int b) {
  return a + b;
}

void argumnet() {
  addNum(3, 4);
}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => ScreenA(),
        '/b': (context) => ScreenB(),
        '/c': (context) => ScreenC(),
      },
    );
  }
}

// ScreenA.dart
import 'package:flutter/material.dart';

class ScreenA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ScreenA'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                primary: Colors.blue,
              ),
              child: Text('Go to ScreenB'),
              onPressed: () {
                // pushNamed의 첫 번째 인자 값인, context는 ScreenA 위젯의 context(위치)다, routeName인 '/b'는 route의 key값
                Navigator.pushNamed(context, '/b');
              },
            ),
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                primary: Colors.blue,
              ),
              child: Text('Go to ScreenC'),
              onPressed: () {
                Navigator.pushNamed(context, '/c');
              },
            )
          ],
        ),
      ),
    );
  }
}

// ScreenB.dart
import 'dart:developer';
import 'package:flutter/material.dart';

class ScreenB extends StatefulWidget {
  @override
  _ScreenBState createState() => _ScreenBState();
}

class _ScreenBState extends State<ScreenB> {
  // StatefulWidget을 생성해서, 이 위젯이 위젯트리에 삽입되자마자 곧바로 initState 메서드가 호출된다
  // 그래서 만약 우리가 앱이 실행되자마자, 즉, StatefulWidget이 생성되는 순간에 무언가 기능을 구현하고 싶다면
  // initState 메서드 내에서 구현을 해주거나,
  // 무언가 필요한 메서드를 호출해주면 된다!
  @override
  void initState() {
    super.initState();
    print('initState is called');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('didChangeDependencies is called');
  }

  @override
  void setState(fn) {
    super.setState(fn);
    log('setState');
  }

  @override
  void didUpdateWidget(covariant ScreenB oldWidget) {
    super.didUpdateWidget(oldWidget);
    log('didUpdateWidget');
  }

  @override
  void deactivate() {
    super.deactivate();
    log('deactivate');
  }

  // dispose 메서드는 위젯이 위젯 트리에서 완전히 제거될 때 호출된다
  @override
  void dispose() {
    super.dispose();
    print('dispose is called');
  }

  @override
  void reassemble() {
    super.reassemble();
    log('reassemble');
  }

  @override
  Widget build(BuildContext context) {
    // StatefulWidget이 초기화되었다면 실제적으로 위젯을 build해야한다
    print('build is called');
    return Scaffold(
      appBar: AppBar(
        title: Text('ScreenB'),
      ),
      body: Center(
          child: ElevatedButton(
        child: Text('Go to ScreenA'),
        onPressed: () {
          setState(() {
            print('setState is called');
          });
          // pop 메서드에 위해 ScreenB 위젯이 파괴되고
          // dispose 메서드가 실행되면서 print 메세지가 출력된다
          Navigator.pop(context);
        },
      )),
    );
  }
}

// ScreenC.dart
import 'package:flutter/material.dart';

class ScreenC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ScreenC'),
      ),
      body: Center(
        child: Text(
          'ScreenC',
          style: TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}

profile
👨🏻‍💻☕️ 🎹🎵 🐰🎶 🛫📷

0개의 댓글