[Flutter] StateFul 생명주기(LifeCycle)

송상민·2022년 9월 25일
0

Flutter

목록 보기
2/13
post-thumbnail

Flutter의 생명주기를 알아보기로 하자.

참고링크


생명주기란?

생명주기의 사전적 의미는 개념 형성에서부터 사용 정지에 이르기까지의 발전상의 변화의 전 과정을 의미한다. 생명주기를 몰라도 앱 개발이 가능하지만 아는 것이 개발할 때 여러모로 유용하다.

State

State란 StatefulWidget에 대한 논리 및 내부 상태를 의미한다.
State의 개념은 두가지로 정의 된다.

  • 위젯이 사용하는 데이터는 변경 될 수 있다.
  • 위젯이 빌드 될 때 데이터를 동시에 읽을 수 없다.

StatelessWidget

한번 위젯을 생성하고 위젯의 구성요소와 속성들이 변화하지 않을 시 StatelessWidget이 사용된다. StatelessWidget은 build 될 때 build 메서드를 호출하기에 build 메서드를 신경써줘야한다.

StatelessWidget LifeCycle

StatefulWidget

StatefulWidget과 StatelessWidget의 가장 큰 차이는 State(상태)가 변화하냐 안하냐의 차이다. State(상태)가 지속적으로 변하거나 위젯의 구성요소들이 지속적으로 변한다면 StatefulWidget을 사용하여 변화하는 것을 계속 업데이트 되는 화면을 통해 보여줄 수 있다.

StatefulWidget LifeCycle

StatefulWidget의 생명주기는 총 8단계로 구분 됩니다.

1.createState()

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

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

Mounted(true)

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

백그라운드에서 진행 되는 작업

2.initState()

위젯이 트리에 속하면 처음으로 호출되는 method다. initState()는 오직 한번만 호출 된다. 또한 반드시 super.initState()를 호출해야한다.

initState에서 실행되면 좋은 것들

  • 생성된 위젯 인스턴스의 BuildContext에 의존적인 데이터 초기화
  • 동일 위젯트리내에 부모위젯에 의존하는 속성 초기화
  • Stream 구독, 알림변경, 또는 위젯의 데이터를 변경할 수 있는 다른 객체 핸들링
  • HTTP request 관리

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

3.didChangeDependencies()

didChangeDependencies() 메서드는 위젯이 최초 생성될 때 iniState 다음에 바로 호출된다.
또한 위젯이 의존하는 데이터의 객체가 호출될 때마다 호출된다.
build 메서드는 항상 didChangeDependencies() 다음에 호출된다.


void didChangeDependencies(){
  super.didChangeDependencies();
  /*state 변경 시 호출*/
}

4.build()

UI를 구현하는 부분으로, 이 메서드는 가장 많이 호출된다. 이 곳에 계산이 필요한 로직이 많이 존재하면 앱의 퍼포먼스는 현저히 낮아진다.

build() 메서드의 특징은 다음과 같다.

  • 반드시 존재해야 한다.
  • 재정의(override) 대상이다.
  • 반드시 Widget을 반환해야 한다.

Widget build(){
  return Container(...)
} 

5.didUpdateWidget()

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

새 위젯과 이전 위젯을 비교할 때 유용함

6.setState()

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

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

7.deactivate()

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


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

8.dispose()

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


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

mounted(false)

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


생명주기 예시 코드

아래 코드는 생명주기 예시 코드로, 참고하여 어떤 흐름을 갖고 있는지 확인해보면 될 것 같다.

// 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 {
  
  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 {
  
  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 {
  
  _ScreenBState createState() => _ScreenBState();
}

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

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

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

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

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

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

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

  
  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 {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ScreenC'),
      ),
      body: Center(
        child: Text(
          'ScreenC',
          style: TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}
profile
실력있는 Flutter 개발자가 되어보자

0개의 댓글