3 분 소요

StatefulWidget의 생명 주기


위젯이 생성되고 화면에 표시되고 유저와의 상호작용할 때 일어나는 이벤트 순서를 나타낸다. 생명 주기는 “집을 지으며 사용하는 과정”을 생각해볼 수 있다.

  1. 집을 설계하고 지으면서 초기화하기 (생성 - createState):

    집을 지을 때 가장 먼저 하는 일은 집을 설계하고 건축을 시작하는 것! 이것은 StatefulWidgetcreateState 메서드에 해당한다. 즉, 위젯 객체가 생성되고 초기화된다.

  2. 집을 꾸미고 가구를 배치하기 (초기화 - iniState):

    집이 지어지면 초기 설정 및 장식 작업을 진행한다. 이것은 iniState 메서드에 해당하는데 위젯은 초기 데이터를 로드하거나 설정하는 등의 작업을 수행한다.

  3. 집의 내부 업데이트하기( 업데이트 - didUpdateWidget) :

    집이 지어진 후에도 가끔은 내부를 업데이트해야 할 때가 있다. 예를 들어, 가구를 추가하거나 새로운 장식품을 설치하는 것과 같이 외부 구조를 건드리지 않고 내부를 변경한다.

  4. 집의 상태를 변경하고 다시 그리기 (재구성 - setState):

    집이 사용되는 동안 일부 변경이 필요할 때가 있다. 예를 들어, 방의 온도를 조절하거나 창문을 열고 닫는 것과 같이 집의 상태를 변경할 수 있다. 이것은 setState 메서드를 호출하여 다시 그려 변경 사항을 반영하는 것에 해당한다.

  5. 집을 파괴하고 정리하기 ( 파괴 - dispose)

    집을 사용하다가 더 이상 필요하지 않을 때 집을 파괴하고 정리해야 할 때가 많다. 예를 들어, 이사를 가거나 집을 철거하는 것과 같이 집을 정리하고 리소스를 반환해야 한다.

이를 코드 예제로 살펴보자.

import 'package:flutter/material.dart';

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

class _MyWidgetState extends State<MyWidget> {
  int _temperature = 20; // 방의 온도

  // 생성 단계: 집을 지을 때 처음으로 호출됨
  @override
  void createState() {
    super.createState();
    // 집의 구조를 설계하고 초기화
    // 집의 구조에는 화장실, 주방, 거실 등이 포함될 수 있음
  }

  // 초기화 단계: 집을 지은 후 내부를 꾸미고 초기 설정을 진행함
  @override
  void initState() {
    super.initState();
    // 초기화 작업, 데이터 로딩, 상태 설정 등을 수행
    // 방을 꾸미고 가구를 배치
    _temperature = 20; // 초기 온도 설정
  }

  // 업데이트 단계: 집의 내부를 업데이트할 때 호출됨
  @override
  void didUpdateWidget(covariant MyWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 가구나 장식품을 추가하거나 변경
    // 집의 외부 구조는 그대로, 내부만 변경
  }

  // 재구성 단계: 집의 상태를 변경하고 다시 그릴 때 호출됨
  void increaseTemperature() {
    setState(() {
      _temperature += 5; // 온도를 증가시키고 화면을 다시 그림
    });
  }

  // 파괴 단계: 집을 정리하고 리소스를 반환할 때 호출됨
  @override
  void dispose() {
    super.dispose();
    // 집을 철거하고 리소스를 반환
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My Widget'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '방의 온도:',
            ),
            Text(
              '$_temperature°C',
              style: TextStyle(fontSize: 48),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          increaseTemperature(); // 온도를 증가시키는 동작
        },
        tooltip: '온도 증가',
        child: Icon(Icons.add),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: MyWidget(),
  ));
}

initState( )


Flutter 프레임워크의 StatefulWidget의 생명 주기 중 하나로, 위젯이 생성되고 초기화될 때 호출되는 메서드다. 이 메서드는 위젯이 화면에 나타나기 전에 필요한 초기 설정 및 데이터 로딩과 같은 작업을 수행하기에 적합하다.

플러터 공식 문서에 의하면 불편하겠지만 build ( ) 메서드 안에서 API 호출을 하는 것을 추천하지 않는다. 이유로는 플러터는 build ( ) 를 UI에 변경이 필요할 때 마다build( ) 를 호출하기 때문이다. 그런데 놀랍게도 이는 자주 일어난다고 한다. 따라서 build ( ) 내에 Api 메서드를 두게 될 경우, ui를 계속 반복해 그리게 되어 앱이 느리게 작동하도록 하는 원인이 될 수 있다.

여기서 API 호출은 외부 서비스나 데이터에 접근하기 위해 서버에 요청(Request)을 보내는 동작을 의미한다.

따라서 Api 호출 결과를 상태 변수에 저장하면 해당 Future가 한 번만 실행되고 그 이후의 다시 빌드에서는 캐시되므로 성능을 향상시킬 수 있다고 한다.

여기서 Cach란 데이터나 결과를 일시적으로 저장하는 메모리 공간을 나타낸다.

프로젝트에 적용하기

내가 생각한 문제는 Api 호출을 현재 다음과 같이 build 메서드 내에서 사용하고 있어서 호출이 계속 일어나서 rebuild 되는 문제가 있지 않을까 해서 리버팟 공식 문서의 FutureProvider를 다시 읽어봤다.

프로바이더 1

리버팟 공식 문서에 따르면 FutureProvider 객체를 생성하고 build메서드 안에서 호출하여 사용하고 있는데, Future 객체(서버에 요청한 데이터를 받아오게 되면)가 완료되면 자동적으로 ui를 다시 그리게 되고 동시에 이 객체를 사용하려는 다른 위젯들이 있다면 Api를 재호출하지 않고 해당 객체를 사용할 수 있다고 한다.

플러터 공식 문서에 따르면 initState 메서드는 위젯이 화면에 나타나기 전에 필요한 초기 설정 및 데이터 로딩과 같은 작업을 하는 역할인데, 따라서 Api 호출을 여기서 선언해놓으면 Api 메서드가 한 번만 호출되므로 ui가 rebuild 되지 않아서 앱이 느려지는 것을 막을 수 있다고 했다. 따라서 해당 내용에 맞게 로직을 짜봤지만 혹시나 싶어서 위 리버팟 공식문서를 봤더니 build 메서드 내에서 FutureProvider 를 호출하고 있다.

우리 비즈니스에 들어가는 데이터는 일주일에 한 번만 업데이트 되므로 앱의 성능에 크게 문제가 되지 않을 것이라 판단돼서 해당 리팩토링을 잠시 중지하기로 했다!

프로바이더 2

댓글남기기