Group Study (2023-2024)/Flutter

[Flutter] 4주차 스터디_StatefulWidget과 BuildContext

김선화김선화 2023. 11. 27. 18:45

1. StatefulWidget

Stateless Widget -  변경 가능한 상태가 필요하지 않은 위젯이며, 사용자 인터페이스를 보다 구체적으로 설명하는 위젯 집합을 구축하여 인터페이스의 일부를 설명한다. 화면이 로드될 때 한 번만 그려지는 State가 없는 위젯이다. 

Stateful Widget - 변경 가능한 상태를 가지는 위젯이며, 위젯이 빌드될 때 동시에 읽을 수 있고 setState를 사용해 상태에 따라 변할 데이터를 보여준다. stateless 위젯과 달리 두 하위클래스 StatefulWidget과 State로 구성되어 있다.

 

  • setState(() {})
    위젯의 상태가 State 객체에 저장되어 위젯의 상태와 모양을 분리한다.  위젯의 상태가 변경되면 상태 자체가 setState() 를 호출하여 해당 프레임워크에 위젯을 다시 그리도록 지시한다.
void _toggleFavorite() {
  setState(() {
    if (_isFavorited) {
      _favoriteCount -= 1;
      _isFavorited = false;
    } else {
      _favoriteCount += 1;
      _isFavorited = true;
    }
  });
}

setState 는 State 클래스에게 데이터가 변경되었다고 알리는 함수이다. 즉 setState 를 사용하여 state가 반응해서 스스로 새로고침할 수 있도록 한다. 


위 코드처럼 데이터에 대한 변화 값들을 setState(() {}) 안에 넣어도 되지만 아래 코드처럼 우선 변화 코드를 작성한 후에 setState(() {}) 만 작성해도 작동한다.

void onClicked() {
    numbers.add(numbers.length);
    setState(() {
    });
  }

하지만 코드의 가독성을 위해 대부분 setState 내에 변화 코드를 작성하는 것이 일반적이다.

  • Statefulwidget의 lifecycle

플러터에서 위젯들은 생성부터 소멸까지의 생명주기, 즉 lifecycle을 가지고 있다. Stateful Widget과 Stateless Widget의 State , 상태의 변화 여부이다. Stateful Widget의 생명주기를 통해 계속 변화하는 요소를 화면을 통해 나타낼 수 있다.

아래는 Lifecycle의 단계이다.

  •  createState()

statefulWidget을 빌드하라는 지시를 받으면 반드시 존재해야 하는 createState()를 호출한다. StatefulWidget은 이것보다 거의 복잡할 필요가 없다.

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}
  •  mounted == true

createState가 state 클래스를 생성하면, buildContext가 state에 할당된다, 모든 위젯은 bool this.property의 형식을 가지고 있으며 buildContext가 할당되면 true로 바뀐다. 

  •  initState()

클래스 생성 이후 위젯이 생성될 때 호출되는 첫번째 매서드이다. initState 는 한 번만 호출되며, 반드시 super.initState()를 호출해야 한다.

@override
initState() {
  super.initState();
  // Add listeners to this class
  cartItemStream.listen((data) {
    _updateWidget(data);
  });
}
  • didChangeDependencies()

initState 위젯이 처음 생성되었을 때 바로 호출되는 메서드이다.  또한 위젯이 의존하는 데이터의 객체가 호출될 때마다 호출된다.

  • build()

필수로 자주 호출되는 메서드이며 @override를 반환해야 하는 위젯이다. 대표적으로 'Padding', 'Center' 등의 child 가 포함된 위젯이다.

  • didUpdateWidget()

부모 위젯이 변경되면 다른 데이터를 제공해야 하기 때문에 이 위젯을 다시 작성해야 하는 경우이며, 동일하게 다시 작성되는 runtimeType의 경우 이 메서드가 호출된다.

@override
void didUpdateWidget(Widget oldWidget) {
  if (oldWidget.importantProperty != widget.importantProperty) {
    _init();
  }
}
  • setState()

Flutter 프레임워크와 개발자로부터 자주 호출되며, "데이터가 변경되었음"을 프레임워크에 알리는 데 사용된다. 이런 경우 build context 의 위젯을 다시 빌드하게 된다.  

void updateProfile(String name) {
 setState(() => this.name = name);
}
  • deactivate()

state가 트리에서 삭제되기 전에 주로 호출된다. 하지만 현재 프레임 변경이 완료되기 전에 다시 삽입 될 수 있다. 
거의 사용되지 않지만 기본적으로 State 객체가 tree의 한 지점에서 다른 지점으로 이동할 수 있기 때문에 존재한다. 

  • dispose()

State 객체가 영구히 제거될 때 호출된다.

  • mounted == false

이 상태에서 state 객체는 remount될 수 없으며, setState() 가 호출되면 에러가 발생한다.


2. BuildContext

  • flutter 위젯트리

Flutter 에서는 거의 모든 것이 위젯이다. 레이아웃 모델, 앱에 표시되는 이미지, 아이콘, 텍스트 뿐만 아니라 보이지 않는 정렬, 행, 열, 그리드까지 위젯이다. 

위 이미지들을 보면 두번째 이미지는 첫번째 이미지의 시각적 레이아웃을 표시하며 icon과 lable 이 포함된 한 개의 row 안에 있는 3개의 colum 을 보여준다. 

위 그림은 Flutter가 어떻게 App을 렌더링하는지 보여주는 그림이다. App 클래스가 수많은 children을 가지고, 그 children이 또 그들의 children을 가지는 구조이다.  

우리가 vsCode 내에서 코드를 작성할 때에도 이렇게 레이아웃과 함께 위젯 tree 구조를 확인할 수 있다.

  • context 개념

Flutter는 앱의 모든 스타일을 한 곳에서 지정할 수 있는 기능을 제공한다. 

BuildContext는 간단히 말하면 위젯 트리에서 위젯의 위치를 다루는 것이다. 
Context 는 해당 child 이전에 있는 모든 상위 요소들에 대한 정보. 즉 부모 요소들의 모든정보, 위젯트리의 정보를 가지고 있다. 매우 먼 요소의 데이터를 가지고 올 수 있기 때문에 유용하다.

@override
  Widget build(BuildContext context) {
    return Text(
      'My Large Title',
      style: TextStyle(
        fontSize: 30,
        color: Theme.of(context).textTheme.titleLarge?.color,
      ),
    );
  }

위의 예시 코드처럼 모든 위젯은 StatelessWidget.build 또는 State.build 에 의해 반환되는 widget의 부모가 되는 자신만의 BuildContext가 있다. 

  • context 접근
class _AppState extends State<App> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        textTheme: const TextTheme(
          titleLarge: TextStyle(
            color: Colors.red,
          
// 생략        

class MyLargeTitle extends StatelessWidget {
  const MyLargeTitle({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Text(
      'My Large Title',
      style: TextStyle(
        fontSize: 30,
        color: Theme.of(context).textTheme.titleLarge?.color,
      ),
    );
  }
}

위 코드에서 MaterialApp 부분을 보면 theme 요소를 사용한 것을 볼 수 있다. 위 부모 요소 _AppState의 state에 theme 에서 자식 요소 MyLargeTitle widget에 접근하고자 한다.  지금 위치가 무슨 위젯인지, 부모의 부모 요소까지 접근 가능하다. 

color : Theme.of(context).textTheme.titleLarge?.color

코드를 그대로 읽어보면 color 의 값을 context의 Theme에서 불러오며, 뒤로 하위 요소들이 이어져 있다. _AppState theme의  textTheme의 titleLarge 의 color를 읽어 접근하라는 뜻이다. 
이 때 '?' 은 null 값에 접근하는 경우를 막기 위해 color가 존재하면 사용하라는 의미이다. 

정리하자면 BuildContext는 위젯 트리에서 위젯의 위치를 제공하고 이를 통해 상위 요소  데이터에 접근할 수 있다.

 

참고자료
https://flutterbyexample.com/lesson/stateful-widget-lifecycle
https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html
https://nomadcoders.co/flutter-for-beginners/lectures/4149
https://docs.flutter.dev/ui/interactivity
https://docs.flutter.dev/tools/devtools/inspector