공부/Flutter

Flutter 스터디 16 Stateful Widget

_룰루 2023. 2. 5. 13:53

조금 매운맛 1 Stateful widget pt 1

 

State

: 방에 어떤 가구들이 배치된 상태로 쭉 유지되고 있는 것.

0.1%로라도 가구의 배치가 바뀌게 되면 다시는 이전 배치로 돌아갈수 없음

 

- State는 데이터이다?

- State란 UI가 변경되도록 영향을 미치는 데이터이다.

- App 수준과 Widget 수준의 데이터가 있다.

App 수준의 데이터 : 우리가 아는 일반적이 데이터에 가까움. 예시로 사용자 인증, 서버에 저장된 데이터를 가져오는 것

Widget 수준의 데이터 : Ratio 버튼을 선택 했느냐 안했느냐, Textfield에 텍스트가 입력됐는지 안 됐는지 등 행위. 즉, App의 상태를 바꾸는 모든 행위

 

 

Stateless widget

- State가 변하지 않는 위젯 = 객체화된 Stateless widget 내 데이터들은 변화하지 않는다.

즉, 한번 지정된 레이아웃, 컬러, 텍스트 등 정보 데이터들 위젯이 rebuild 되지 않는 이상 변하지 않음

 

* 여기서 의문?

Text('Korea') => Text('France')

우리가 텍스트 내의 문자를 변경할 수 있는데 왜 변화할 수 없다고 하는가?

Flutter의 Hot reload : 코드 변경 후 저장과 동시에 애뮬레이터 등에 변화를 빠르게 보여줌.

 

Widget Tree Element Tree Render Tree
코드 상에서 컨트롤 가능  플러터가 내부적으로 컨트롤 플러터가 내부적으로 컨트롤, 
  Widget들의 정보를 담고 있는 하나의 객체  
  Widget tree를 근거해서 생성됨 Widget tree를 근거해서 생성됨
설계도 같은 역할. 플러터에게 어떤 UI로 그려달라고 알려주는 역할 플러터가 Widget Tree를 근거로 생성해주는 것.

직접적으로 화면에 그려주는 역할
  중간에서 Widget Tree와 Render Tree를 연결하는 역할.  

하나의 구성일 뿐, 직접적으로 화면에 표현되는 것은 아님.

Widget Tree의 모든 widget에 대해 1대1 맞대응 형식으로 하나하나 모두 연결되어 있음.
 
  Reder Tree가 실제적으로 랜더링 하기 위해서 가지고 있는 Render 객체와 1대1 맞대응 형식으로 연결되어 있음 눈으로 보는 화면상의 모든 거슨 Render Tree의 결과물

예시)

1. Stateless widget 내에 Container가 있다고 가정

2. 이에 대응하는 Container element를 Element Tree 상에 추가함.

Container element는 Widget tree 상에 Cotainer Widget을 포인트, 즉 가르키게 됨. (C 언어의 Pointer와 같은 개념)

하지만 element의 특징은 그냥 위젯을 가리키고만 있는 것이 아니라 그 위젯의 정보(그림상 Contatiner Widget의 정보. 위치, 타입, 가로크기, 세로크기, 배경색 등)도 함께 가지고 있음.

 

 

Widget tree and Element Tree

Widget tree는 build 메소드가 호출될 때마다 새롭게 Rebuild 됨.

 

- Reload vs. Rebuild

Reload : 어떤 프레임을 그대로 둔 채 부수적인 요소들만 바꾸는 것. 자동차를 예시로 타이어

Rebuild : 자동차를 새로 구입 하는 것. 

 

widget tree가 매번 rebuild 된다는 것은 widget tree 자체도 stateless widget처럼 한번 생성되면 바뀌지 않기 때문에 바꾸려면 아예 새로 만들어야한다는 의미이며, widget tree 내에 모든 위젯들도 rebuild 된다는 의미.

 

사소하게 텍스트 문자 하나만 바꾸어도 hot reload 때문에 매번 build 메소드가 호출되면서 widget tree가 rebuild 됨.

여기에 연결된 element tree와 render tree 까지 rebuild 된다면 최악의 포포몬스를 내는거임

 

하지만 이 문제를 Element tree가 해결해줌

새로운 컨테이너 추가 이전의 컨테이너를 버리고 새로운 컨테이너로 링크

Element tree는 widget tree 구조를 그대로 복사하기에 링크되었던 위젯의 위치, 타입 등 여러 정보를 가지고 있음. 그래서 어떤 위젯의 새롭게 추가되는 코드 때문에 build 메소드가 호출되고 rebuild 된다면 Element tree도 같이 rebuild 되는 것이 아니라 새롭게 생성된 위젯의 타입과 위치를 바탕으로 일치할 때에 한해서 링크가 업데이트 함.

 

타입과 속성 등은 같지만 새로운 코드 추가로 다시 랜더링이 필요한 위젯에 한해서 Reder tree에 정보를 전달해주고 이를 근거로 기존 위젯의 Render 객체에서 바뀐 부분만을 다시 그려줌

 

다시 정리겸 예시

1. 컨테이터 위젯 배경색이 하얀색에서 파랑색으로 바뀜

2. 이를 반영하기 위해서 Hot reload가 실행

3. 이를 위해서 build 메소드 호출

4. 이어서 widget tree가 rebuild 되면서 widget tree 상의 모든 위젯도 rebuild 됨.

5. Element tree는 rebulid 된 위젯들을 기존 정보와 비교하여 일치하면 새로 업데이트 된 링드들만 업데이트

6. 최종적으로 다시 랜더링이 필요한 위젯들에 한해서 Element tree의 정보들을 골라

7. Render tree에 넘겨주고

8. Render Tree는 색상만을 바꾸어서 다시 그려줌(리랜더링)

 

hot reload는 전체 다시 그리는 것이 아니라 Element tree를 사용해서 변경된 부분만을 다시 그리기 때문에 빠르고 효율적

 

- Stateless widget은 한번 빌드되면 절대로 변하지 않기에 rebuild 만을 통해서 새로운 state 적용 가능.

Stateless widget인 텍스트를 바꾸는 것도 생성자를 통해서 외부의 데이터를 전달하는 것뿐 텍스트 내부의 state가 바뀌는 것이 아님. 최종적으로 hot reload를 통해서 build 메소드가 호출되면 텍스트 위젯은 리빌드 되고 Element tree는 이 정보를 Render tree에 전달해서 Text Widget의 바뀐 내용을 화면에 출력.

- Flutter는 초당 60프레임 속도 > 빠르게 반영가능

 

 

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  int counter = 0; //counter 선언
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.lightBlue,
      ),
      home: Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('you have pushed the button this many times:'),
              Text(
                  '$counter', // 변수 출력
                style: Theme.of(context).textTheme.displayMedium,
                  )
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: (){ // 버튼 누르면 counter 1씩 증가
            counter++;
            print('$counter'); //콘솔창에 증가된 숫자 출력
          },
        ),
      ),
    );
  }
}

> MyApp은 Stateless 위젯이므로 immutable(불변)한 변수를 넣으면 안됨.

이 상태에서 floatingactionButton 누르면 

화면에서는 변화가 없음

하지만 콘솔창에서는 잘 뜸. 이상태에서 hot reload (안드로이드 스튜디오 번개모양) 누르면

이제까지 눌렀던 count의 숫자가 반영이 됨

 

Hot reload를 통해 Myapp 위젯의 build 메소드가 호출되고 widget tree가 rebuild 되는 과정에서 text 위젯도 리빌드 됨.

즉, 버튼의 클릭을 통해서 변수값이 변한걸 감지한 element tree가 변수값을 출력하고 있는 text 위젯의 내용을 바꾸도록 render tree에게 전달해준 것

 

 

Extend(상속)

: 부모 클래스를 상속받은 자식 클래스는 부모 클래스의 기능을 가져다 쓰거나 변형해서 쓸 수 있음.

 


조금 매운맛 2 Stateful widget pt 2

 

Stateful / Stateless 공통점 : 생성자를 통해서 외부에서 데이터가 입력이 되면 그 결과를 반영하기 위해서 build 메소드가 호출 되면서 위젯들이 리빌드 되고 필요한 부분의 UI를 랜더링 함.

차이점은 Stateful 위젯은 내부에 State라는 위젯을 따로 가지고 있음.

 

Stateful 위젯에서 build 메소드는 state 위젯이 가지고 있음. 즉, state 위젯이 build 메소드를 호출해여 리빌드 하고, 랜더링 하게 만듦

 

Stateful 위젯이 rebuild 되는 경우

1. state 위젯처럼 child 위젯의 생성자를 통해서 데이터가 전달 될 때

2. Internal state가 변할 때

 

 

아래 카운트 하는 코드를 하나씩 고쳐 Stateful widget에 맞게 고치기

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  int counter = 0; //counter 선언
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.lightBlue,
      ),
      home: Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('you have pushed the button this many times:'),
              Text(
                  '$counter', // 변수 출력
                style: Theme.of(context).textTheme.displayMedium,
                  )
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: (){ // 버튼 누르면 counter 1씩 증가
            counter++;
            print('$counter'); //콘솔창에 증가된 숫자 출력
          },
        ),
      ),
    );
  }
}

 

 

 

> Stateful Widget은 Widget class도 상속 받고 있음. 이는 statefulwidget도 immutable(불변), 한번 상속 받으면 바뀌지 않는 속성도 함께 가지고 있음.

하지만 Stateful Widget은 state의 변화를 반드시 반영해야 함. 이를 위해서 두개의 클래스로 나눠서 MyApp 위젯은 immutable한 속성을 가지도록, MyAppState는 mutable한 속성을 대신하도록 만들어 보겠음

 

이 두 클래스를 연결하는 방법

1. state 클래스는 Generic 타입으로 되어 있음

>  MyAppState 클래스는 state 클래스를 상속 받았고 state 타입이 되었음.

상속받은 state 클래스의 generic 타입을 MyApp 클래스로 지정해준다면 이 state 클래스는 오직 MyApp 타입만을 가질수 있게 됨. (flutter 10 글의 slot 클래스의 generic 타입을 circle로 지정해주면 원기둥만 통과할 수 있는 것과 같은 이치)

그렇다면 MyAppstate 클래스는 Stateful 위젯인 MyApp에 연결되는 것이라고 플러터에게 알려주는 것

(?????????????? 제대로 이해못함)

 

그렇다면 왜 State 클래스는 제네릭 타입으로 지정되어 있는가?

예시) Checkbox

> Checkbox는 stateful widget을 상속 받고 있음

 

> CheckboxState 클래스가 상속 받은 State 클래스의 제네릭 타입이 checkbox 클래스로 되어 있음

=> state 클래스를 제네릭 타입으로 만들어서 편하게 지정하고, 그 외의 타입을 전달 받지 않도록 하는 안전장치도 겸

 

2. MyApp 위젯 내에서 다른 메소드 불러와서 연결

=> createState method

createState 메소드는 State 타입의 객체를 리턴해야함. 결국 Stateful widget 타입만이 올 수 있는 객체이어야 하며 이 객체는 MyAppState 클래스에 근거해서 만들어진 것.

createState 메소드는 Stateful widget이 호출될 때마다 생성되는 객체

 

setstate method

1. 매개변수로 전달된 함수를 호출

2. build 메소드를 호출

 

> floatingActionbutton의 onpressed 메소드 안에 setState 메소드 추가 및 counter와 출력을 그 안으로 옮겨줌

>> 이제 버튼을 누르면 1씩 증가할 것이고, 변화를 감지한 setstate 메소드가 호출 됨.

이떄 setstate는 카운터 숫자가 증가함에 따라서 카운터 변수를 사용하고 있는 위젯들을 화면에 표시해줌

=> 숫자의 증가로 state가 변했고 이 변화를 반영해서 리빌드 하기 떄문

 

dirty : setstate가 표시해준 숫자들을 일컫는 말. 변화하는 숫자를 출력해주는 텍스트를 뜻함

dirty

 

 

<숙제>

// 플로팅액션버튼 변경

Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    FloatingActionButton(onPressed: (){
        setState(() {
          counter++;
          print('$counter');
        });
      },
      child: Icon(Icons.add),
    ),
    FloatingActionButton(onPressed: (){
        setState(() {
          counter--;
          print('$counter');
        });
      },
      child: Icon(Icons.remove),
    ),
  ],
),

무사히 잘 작동은 된다...

간단한거라고 했찌만 아직까지 위젯들에 대해서 제대로 머리에 정리가 안되어서 그런지 이런저런 시도하고 구글링도 한다고 시간이 꽤 지났음.. 공부하자 공부

반응형