와챠의 우당탕탕 코딩 일기장

[Flutter] Flutter 로 웹툰 앱 만들기: State/buildContext/Widget Life Cycle 본문

코딩 일기장/Flutter

[Flutter] Flutter 로 웹툰 앱 만들기: State/buildContext/Widget Life Cycle

minWachya 2023. 1. 27. 15:07
반응형

목차

  1. 요약
  2. 버튼 클릭 시 카운트 증가: Stateful Widget
  3. 버튼 클릭 시 Text ui 증가: StatefulWidget + 반복문
  4. buildContext: 부모에서 정의한 Theme을 자식이 사용하게 하기
  5. 버튼 클릭 시 ui(Text) 변경: Widget Life Cycle

1. 요약

StatelessWidget: build 메서드를 통해 ui 출력

StatefulWidget: 상태에 따라 데이터 변하고, 이에따라 ui도 변경

widget life cycle을 가지고 있다.

setState(): State클래스에서 데이터 변경됨을 알리는 함수, build가 다시 실행됨

 

BuildContext context: 부모 요소에 쉽게 접근 가능


2. 버튼 클릭 시 카운트 증가: Stateful Widget

class App extends StatelessWidget{...} // 에서 커맨트 + . 눌러 stateful widget으로 변경
=>
class App extends StatefulWidget{...}

앞에서는 StatelessWidget을 사용했는데, 커맨드 + . 을 눌러 StatefulWidget으로 변경해보면, 

void main() {
  runApp(const App());
}

class App extends StatefulWidget {
  const App({super.key});

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {

  @override
  Widget build(BuildContext context) {...}
}

이렇게 된다.

 

State<App>을 상속받은 _AppState를 보자.

여기서 상태를 관리하고, 상태에 따른 ui 변경을 해볼 것이다.

 

카운터 변수를 하나 만들고,

버튼 클릭 시 카운터가 하나 증가하는 함수를 만들어서

ui에 적용해주면 완성..!!

class _AppState extends State<App> {
  int counter = 0;		// 상태 변수(그냥 Dart 프로퍼티이다.)

// 클릭 시 변할 부분을 setState 안에 써준다.
  void onClicked() {
    setState(() {
    // 카운트 1씩 증가
      counter = counter + 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        ...
        body: Center(
          child: Column(
            ...
            children: [
              ...
              // 카운터
              Text('$counter'),
              // 버튼에 클릭 메서드 달기
              IconButton(
                iconSize: 40,
                onPressed: onClicked,
                icon: const Icon(
                  Icons.add_box_rounded,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

3. 버튼 클릭 시 Text ui 증가: StatefulWidget + 반복문

 

 

 

_AppState 내부에 number 배열을 만들고,

클릭 시 배열에 숫자를 추가도록 하는 함수를 만들다.

List<int> numbers = [];

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

 

그리고 이걸 for문을 사용해 출력해주면 완성,,,,

for문만으로 ui가 아름답게 생성되다니..

  for (var n in numbers) Text('$n'),

4. buildContext: 부모에서 정의한 Theme을 자식이 사용하게 하기

부모에서 Text color가 red인 Theme을 생성했다.

이 Theme 을 사용해 자식도 Text color를 변경하려고 한다.

이를 설명하기 위해 MyLargeTitle이라는 위젯을 만들었다.

class _AppState extends State<App> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
    // theme 정의
      theme: ThemeData(
        textTheme: const TextTheme(
          titleLarge: TextStyle(
            color: Colors.red,
          ),
        ),
      ),
      home: Scaffold(
        // ...
        body: Center(
          child: Column(
            // ...
            children: const [
              MyLargeTitle(),
            ],
          ),
        ),
      ),
    );
  }
}

 

자식(MyLargeTitle)에서는 아래와 같이 context를 통해 부모의 Theme에 접근할 수 있다.!

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

자식의 전체 코드는 이러함.

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

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

5. 버튼 클릭 시 ui(Text) 변경: Widget Life Cycle

Widget에는 아래와 같은 주요 Life Cycle이 있다.

간단하게 설명하면

  • initState: 초기화 목적. 한 번만 불림
  • build: 위젯 생성
  • dispose: 위젯 사라질 때 불림

_AppState의 변수로 제목을 보일지 안 보일지 정하는 변수를 만들고,

이 변수를 true-false로 변하게 하는 함수를 만들어 IconButton에 적용해준다.

bool showTitle = true;

  void toggleTitle() {
    setState(() {
      showTitle = !showTitle;
    });
  }

그리고 위젯 클래스에

class _MyLargeTitleState extends State<MyLargeTitle> {
  // 부모 요소에 의존하는 데이터를 초기화하는 경우 사용, api 업데이트 등...
  // 한 번만 호출됨
  @override
  void initState() {
    super.initState();
    print('init');
  }

  // 스크린에서 위젯이 사라질 때 호출됨, api 업데이트, 이벤트 리스너 구독 취소
  // 등 무언가를 취소하는 곳...
  @override
  void dispose() {
    super.dispose();
    print('dispose');
  }

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

출력문은 다음과 같다.

// <엡 시작>
init
build
// <눈 버튼 클릭: 제목 사라짐>
dispose
// <눈 버튼 클릭: 제목 생김>
init
build
// <눈 버튼 클릭: 제목 사라짐>
dispose

느낀 점

  • 더 많은 라이프 사이클이 있을텐데 멀까, 안드랑 비슷할까
  • 부모 Theme에 접근할 때 . 을 사용해 깊게 들어가는 건 너무 귀찮고 코드도 길어보이는데(물론 직관적이긴 함) 다른 간단한 방법 없을까?
  • State가 달라질 때마가 ui를 재호출하는 방법은 안드의 그것과 너무나도 비슷하다. 더 간편해보이기도 한다.
  • 비개발자가 보기에 좋을 듯한 강의다. 전공자는 더 심화 버전 들어도 ㄱㅊ을듯,,!
반응형
Comments