2021.06.16 - [Usage/Flutter] - [Flutter] Bloc Pattern (stateful -> bloc -> skip event bloc )

 

[Flutter] Bloc Pattern (stateful -> bloc -> skip event bloc )

BLOC Bussiness Logic Component 비즈니스 로직과 ui를 구분 짓고 필요에 따라 원하는 부분만 데이터 처리 실 사용 예제 버튼 클릭시 증감 1. Stateful version ui widget - 버튼 클릭시 증감 //stateless widge..

mugon-devlog.tistory.com

Install package

https://pub.dev/packages/provider

Project 구조

main.dart
src
 - count_home.dart 
 - count_provider.dart
 - count_home_widget.dart // statelesswidget

main.dart

void main() {
  runApp(MyApp());
    // runApp에서 provider를 불러오면 MyApp에서 접근 가능
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MultiProvider(
        providers: [
          ChangeNotifierProvider(
              create: (BuildContext context) => CounterProvider())
        ],
                // child의 모든 하위 요소들은 CounterProvider에 접근 가능
        child: Home(),
      ),
      // home: ChangeNotifierProvider(
      //   create: (BuildContext context) => CounterProvider(),
      //   child: Home(),
      // ),
    );
  }

count_provider.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// ChangeNotifier 상속
// bloc 에서 stream 처리, sink 주입 했던 것을 처리해줌
class CounterProvider extends ChangeNotifier {
    // private
    // 이곳에서 선언되는 모든 변수는 상태관리 대상
  int _count = 0;
    // get 함수를 사용해서 private한 변수에 접근 가능하도록 만듦
  int get count => _count;
  add() {
    _count++;
        // 상태값이 update 되었다는 신호를 보내줌
    notifyListeners();
  }

  subtract() {
    _count--;
    notifyListeners();
  }
}

count_home.dart

class Home extends StatelessWidget {
  const Home({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
        // Provider.of로 사용할 provider를 가져옴
        // listen을 false로 해주면 현재의 widget은 rebuilding이 안됨
    CounterProvider countProvider =
        Provider.of<CounterProvider>(context, listen: false);
    return Scaffold(
      appBar: AppBar(
        title: Text("Provider"),
      ),
      body: CountView(),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () => countProvider.add(),
          ),
          IconButton(
            icon: Icon(Icons.remove),
            onPressed: () => countProvider.subtract(),
          ),
        ],
      ),
    );
  }

count_home_widget.dart

class CountView extends StatelessWidget {
  const CountView({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
                // Provider.of를 통해 접근해서 count값 가져옴
                // build 전체를 rebuilding 함
        Provider.of<CounterProvider>(context).count.toString(),
      ),
    );
  }
}

// 특정 위젯만 rebuilding
class CountView extends StatelessWidget {
  const CountView({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
     return Center(
            // consumer를 사용해서 특정 위젯만 rebuilding
       child: Consumer<CounterProvider>(
                builder: (context, provider, child) {
         return Text(
           provider.count.toString(),
         );
       }),
    );
  }
}

참고

https://www.youtube.com/watch?v=AmmjdvhQG1s 

 

728x90

'Study > Flutter' 카테고리의 다른 글

[Flutter] Factory 패턴  (0) 2021.07.13
[Flutter] Stateful widget  (0) 2021.06.14
[UI초급] Container, materialApp, Scaffold  (0) 2021.06.07

BLOC

Bussiness Logic Component

비즈니스 로직과 ui를 구분 짓고 필요에 따라 원하는 부분만 데이터 처리

실 사용 예제

버튼 클릭시 증감

1. Stateful version

ui widget - 버튼 클릭시 증감

//stateless widget으로 count를 보내 화면에 그려줌
body: CountViewStateless(count: count),
floatingActionButton: Row(
  mainAxisAlignment: MainAxisAlignment.end,
  children: [
    IconButton(
      icon: Icon(Icons.add),
      onPressed: () {
        setState(() {
          count++;
        });
      },
    ),
    IconButton(
      icon: Icon(Icons.remove),
      onPressed: () {
        setState(() {
          count--;
        });
      },
    ),
  ],
),

view - 결과 출력

import 'package:flutter/material.dart';

class CountViewStateless extends StatelessWidget {
  int count;
  CountViewStateless({Key key, this.count}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print("CountViewStateless Build !!");
    return Center(
        child: Text(
      count.toString(),
      style: TextStyle(fontSize: 80),
    ));
  }
}

2. bloc pattern

ui widget - 값을 변경시키는 이벤트 발생

// 전역변수로 bloc 생성
CountBloc countBloc;

class _BlocDisplayWidgetState extends State<BlocDisplayWidget> {
  @override
  void initState() {
    super.initState();
        // bloc
    countBloc = CountBloc();
  }

  @override
  void dispose() {
    super.dispose();
        // bloc 종료
    countBloc.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("bloc 패턴"),
      ),
      body: CountView(),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () {
                            // add는 이벤트를 등록한다는 의미
                            // 숫자를 더하거나 빼는 로직이 현재파일에는 존재하지 않음
              countBloc.countEventBloc.countEventSink.add(CountEvent.ADD_EVENT);
            },
          ),
          IconButton(
            icon: Icon(Icons.remove),
            onPressed: () {
              countBloc.countEventBloc.countEventSink
                  .add(CountEvent.SUBTRACT_EVENT);
            },
          ),
        ],
      ),
    );
  }
}

count_bloc - 비즈니스 로직 처리

import 'dart:async';

//bloc
class CountBloc {
  CountEventBloc countEventBloc = CountEventBloc();
  int _count = 0;
    // broadcast를 넣어주면 여러군데에서 구독 가능
  final StreamController<int> _countSubject = StreamController<int>.broadcast();

    // count는 _countSubject.stream를 구독하고 있는 모든 widget에게 변경된 상태값을 전달
  Stream<int> get count => _countSubject.stream;

    // 생성자
  CountBloc() {
        // countEventBloc의 _countEventSubject을 구독
    countEventBloc._countEventSubject.stream.listen(_countEventListen);
  }
    // 생성자에서 변화를 감지하여 listen으로 리턴 받은 것
    // 인자값 event에 해당하는 것은 enum에 등록된 이벤트 
  _countEventListen(CountEvent event) {
        // 비즈니스 로직 부분
    switch (event) {
      case CountEvent.ADD_EVENT:
        _count++;
        break;
      case CountEvent.SUBTRACT_EVENT:
        _count--;
        break;
    }
        // 결과값인 _count를 sink에 add로  넣어줌 
    _countSubject.sink.add(_count);
  }

  dispose() {
    _countSubject.close();
    countEventBloc.dispose();
  }
}

// event 
class CountEventBloc {
  final StreamController<CountEvent> _countEventSubject =
      StreamController<CountEvent>();
    // event 값이 sink로 들어오면 _countEventSubject를 통해 구독자에게 알려줌  
  Sink<CountEvent> get countEventSink => _countEventSubject.sink;

  dispose() {
    _countEventSubject.close();
  }
}

// event에 해당하는 enum
enum CountEvent { ADD_EVENT, SUBTRACT_EVENT }

count_view 결과값 받아오는 곳

StreamBuilder(
    // stream을 리스닝 
  stream: countBloc.count, // Stream<int> get count => _countSubject.stream; 구독
  initialData: 0,
    // snapshot으로 값이 들어옴
  builder: (BuildContext context, AsyncSnapshot<int> snapshot) { 
    if (snapshot.hasData) {
      return Text(
        snapshot.data.toString(),
        style: TextStyle(fontSize: 80),
      );
    }
    return CircularProgressIndicator();
  },
),

3. Skip event bloc pattern

ui 위와 다르게 바로 이벤트 호출함

class _BlocDisplayWidgetState extends State<BlocDisplayWidget> {
  @override
  void initState() {
    super.initState();
    countBloc = CountBloc();
  }

  @override
  void dispose() {
    super.dispose();
    countBloc.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("bloc 패턴"),
      ),
      body: CountView(),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          IconButton(
            icon: Icon(Icons.add),
            onPressed: () {
                            // 이벤트 호출
              countBloc.add();
            },
          ),
          IconButton(
            icon: Icon(Icons.remove),
            onPressed: () {
              countBloc.subtract();
            },
          ),
        ],
      ),
    );
  }
}

count_bloc - skip event 비즈니스 로직 처리

import 'dart:async';

class CountBloc {
  int _count = 0;
  final StreamController<int> _countSubject = StreamController<int>.broadcast();
  Stream<int> get count => _countSubject.stream;

  add() {
    _count++;
    _countSubject.sink.add(_count);
  }

  subtract() {
    _count--;
    _countSubject.sink.add(_count);
  }

  dispose() {
    _countSubject.close();
  }
}

참고

https://www.youtube.com/watch?v=AY6i0a4BM7o 

 

728x90

Future 함수 정의

Future<String> myFuture() async{
    await Future.delayed(Duration(seconds: 2));
    return 'another Future completed';
}

FutureBuilder 소스코드 확인

FutureBuilder 인자값 확인 -> future, builder 정의 예정

builder메서드는 아래와 같이 context와 snapshot을 인자값으로 가지고 있음

snapshot은 특정 시점에 데이터를 복사해서 보관하는 것

snapshot이 복사해서 가지고 있는 future 데이터가 실제로 존재하는지 확인 필요

소스코드를 보면 builder는 AsyncSnapshot객체와 함께 제공되는데 이 객체는 connectionState에 3가지 중 한가지 속성을 가진다고 나와있음 → 없을때, 대기중일때 완료되었을때

future가 완료된 상태에서만 화면에 ui를 그릴수 있도록 조건식 생성, 데이터가 아직 도착하지 않았다면 대기중 표시

FutureBuilder(
    future: myFuture(),
    builder: (context, snapshot){
        // future가 완료된 상태일때
        if(snapshot.connectionState == ConnectionState.done){
            return Text(
                snapshot.data,
            );
        }
        // 완료된 상태가 아닐때 progress 출력
        return CircularProgressIndicator();
    }
)
728x90

Thread

프로세스 내에서 실행되는 흐름의 단위

Event loop

Dart는 Single Thread로 운영됨 → Event loop를 통해 복잡한 작업들을 효율적으로 처리

Flutter 앱을 실행시키는 순간 isolate라고 불리는 새로운 thread process가 하나 생성됨

이 thread가 앱 전체를 총괄하는 단일 thread가 됨

thread가 생성되는 순간 dart는 자동적으로 3가지 작업을 하게됨

  1. 내부적으로 First In First Out(FIFO)방식으로 MicroTask 와 Event 준비 MicroTask : event queue로 넘어가기 전에 아주 짧은 시간 동안 비동기적으로 먼저 실행되고 끝나는 작은 작업
  2. 앱의 시작점인 main 함수 실행
  3. Event loop 실행

Dart는 하나의 단일 thread가 실행되는 순간 내부적으로 event loop process를 통해 순서대로 대기열에 있는 micro task와 event를 처리

더 이상 내부적으로 진행할 micro task가 없다면 event loop는 외적으로 전달되는 각종 이벤트 (gesture, drawing, reading files, fetching data, button tap, future, stream 등)를 순서대로 처리하게 됨

각종 외적인 이벤트들이 이벤트 큐에 등록되고 이벤트 루프를 통해 처리

Future

비동기 방식으로 미래의 어느시점에 완성되어서 실제적인 데이터가 되거나 에러를 반환하는 객체

코드상에서 새로운 future를 객체화 시키면

  1. 다트에 의해서 future 객체가 내부적인 배열에 등록
  2. Future 관련해서 실행되어야하는 코드들이 이벤트 큐(대기열)에 등록
  3. 불완전한 future 객체가 반환
  4. Synchronous 방식으로 실행되어야 할 코드 먼저 실행
  5. 최종적으로 실제적인 data 값이 future 객체로 전달

Async method

async 키워드를 사용한 순간

  1. 메서드를 통해서 나오는 결과물들은 future라는 것을 알게 됨
  2. await 키워드를 만날때까지 synchronous 방식으로 코드 처리
  3. await 키워드를 만나면 future가 완료될 때까지 대기
  4. future가 완료 되자마자 그 다음 코드들을 실행

전체 앱의 실행이 중지되지 않고 future 부분은 건너띄고 그 아래에 있는 코드들을 synchronous하게 처리

예시

String createOrderMessage(){
    var order = fetchUserOrder();
    return 'Your order is: $order';
}

Future<String> fetchUserOrder(){
    return Future.delayed(
        Duration(second: 2), ()=>'Large Latte',
    );
}

void main(){
    print('Fetching user order...');
    print(createOrderMessage());
}

실행시 에러 → main()의 두번째 print에서 createOrderMessage()는 미완성의 객체를 반환하고 있기 때문

fetchUserOrder()에서 2초를 기다리고 fetchUserOrder()가 반환해야 하는데 기다리지 않고 즉시 반환하기 때문 → 이를 해결하기 위해 async 키워드 사용

createOrderMessage() 수정

  1. 비동기 방식으로 실행된 결과의 String 값을 리턴해줘야하므로 타입 변경 String → Future
  2. order 변수에 fetchUserOrder 함수의 리턴값을 할당해줄려면 즉시할당하는 synchronous 방식이 아니라 기다렸다가 값이 전달되는 비동기 방식어야함 createOrderMessage()가 비동기 방식으로 처리되어야함을 dart에게 알려줘야함 → async 키워드 추가
  3. fetchUserOrder()의 실행이 끝날때까지 기다렸다가 order 변수에 값을 할당해줘야함 → await 추가
// 1
Future<String> createOrderMessage() async{ // 2
              // 3
    var order = await fetchUserOrder();
    return 'Your order is: $order';
}

main() 수정

  1. main()에서 호출되는 createOrderMessage()가 비동기 방식으로 처리되는 함수이므로 main()도 비동기 방식으로 처리됨을 알려줘야함 → async 추가
  2. createOrderMessage()의 리턴값이 출려되려면 order변수의 값이 할당될 때까지 기다려야함 → await 추가
// 1
void main() async{
    print('Fetching user order...');
    // 2
    print(await createOrderMessage());
}

정리

async 키워드는 future 자체를 대신하는 것이 아니라 비동기 방식으로 실행되는 함수라는 것을 dart에게 알려주는 기능을 하는 것이며 await 키워드와 함께 미래의 어느 시점에 전달될 값을 기다리는 것

future는 하나의 객체로서 객체가 생성되는 순간에는 미완성으로 존재하다가 미래의 어느 시점에 데이터를 전달받은 온전한 객체가 되거나 데이터에 문제가 생긴다면 에러를 반환

심화 예제

void main() async {
  methodA(); // 1
  await methodB(); // 3
  await methodC('main'); // 9
  methodD(); // 12
}

methodA(){
  print('A'); // 2
}

methodB() async {
  print('B start'); // 4
  await methodC('B'); // 5
  print('B end'); // 8
}

methodC(String from) async {
  print('C start from $from'); // 6 'b' // 10 'main'

  Future((){
    print('C running Future from $from'); // 14 'b' // 16 'main'
  }).then((_){
    print('C end of Future from $from'); // 15 'b' // 17 'main'
  });

  print('C end from $from'); // 7 'b' // 11 'main'
}

methodD(){
  print('D'); // 13
}

//////
A
B start
C start from B
C end from B
B end
C start from main
C end from main
D
C running Future from B
C end of Future from B
C running Future from main
c runngin Future from main
///

참고

https://www.youtube.com/watch?v=HjhPhAUPHos&list=PLQt_pzi-LLfoOpp3b-pnnLXgYpiFEftLB&index=12 

 

728x90

'Study > Dart' 카테고리의 다른 글

[Dart] factory 패턴  (0) 2021.06.24
[Dart] const 와 final 이해하기  (0) 2021.06.15
[Dart] Future, Isolate  (0) 2021.06.08
[Dart] 상속을 쓰는 이유  (0) 2021.06.07
[Dart] 상속  (0) 2021.06.07

final 과 const 변수 이해하기

변수

void main(){
    int age;
    age = 20;
}

변수 age의 역할은 20이라는 숫자가 저장된 위치를 포인트 즉, 가리키고 있는 것

20이 저장된 위치의 주소를 저장하고 있는 것이 변수

제어자

final과 const 같은 keyword를 modifier 제어자 라고 함

제어자는 클래스, 변수, 함수를 정의할때 함께 쓰여서 이것들을 사용하기 위한 옵션을 정의해주는 역할

void main(){
    final int myFinal = 30;
    const int myConst = 70;
}

접근제어자

final,const : 변수 값이 한번 초기화되면 바꿀 수 없게 하는 것

void main(){
    final int myFinal = 30;
    const int myConst = 70;

    myFinal = 20; // 에러
    myConst = 50; // 에러
}

final 변수 초기화

  1. 선언할때 초기화
void main(){
    final int myFinal = 30;
}
  1. 객체 생성시에 외부데이터를 받아 생성자를 통해 초기화
class Person {
    final int age;
    String name;

    Person(this.age, this.name);
}

void main(){
    Person p1 = new Person(21, 'Tom'); // 생성자를 통해 할당 -> 이후로 age는 변경 불가
    print(p1.age); // 21
} 

2번이 가능한 이유

final은 초기화되는 시점이 앱이 실행되는 시점이기 때문 ⇒ run time constant

response 변수는 컴파일시에 초기화 되지 않고 앱이 실행된 후 웹 상에서 데이터가 전송될 때까지 기다렸다가 그 후에 값이 저장

Const

compile-time constant는 컴파일 시에 상수가 됨

const 변수는 선언과 동시에 초기화

void main(){
    const time = DateTime.now() // 에러
}

현재 시간은 매번 호출될 때마다 그 값이 변경되기 때문에 런타임시에 값이 지정되어야하므로 const 키워드는 오류 발생

정리

  1. const 변수는 컴파일 시에 상수화
  2. final 변수는 런타임 시에 상수화
  3. Compile-time constant = Run-time constant
    컴파일 시에 상수화는 런타임에도 상수화가 유지됨을 의미
  4. final 변수는 rebuild 될 수 있음

참고

https://www.youtube.com/watch?v=akc51-j84os&list=PLQt_pzi-LLfoOpp3b-pnnLXgYpiFEftLB&index=6

 

728x90

'Study > Dart' 카테고리의 다른 글

[Dart] factory 패턴  (0) 2021.06.24
[Dart] future, async, await, 실행순서  (0) 2021.06.15
[Dart] Future, Isolate  (0) 2021.06.08
[Dart] 상속을 쓰는 이유  (0) 2021.06.07
[Dart] 상속  (0) 2021.06.07

Rendering 조건

stateful widget에서 rebuild를 초래하는 것은 state 클래스이며 이를 통해 스크린을 랜더링함

  • child 위젯의 생성자를 통해서 새로운 데이터가 전달될때
  • internal state가 바뀔때

왜 StatefulWidget 클래스는 두개로 이루어져 있을까?

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Charactor card',
      home: MyPage(),
    );
  }
}

먼저, StatefulWidget클래스는 아래와 같이 widget 클래스를 상속하고 있습니다.

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key? key }) : super(key: key);
  @override
  StatefulElement createElement() => StatefulElement(this);
  @protected
  @factory
  State createState(); // ignore: no_logic_in_create_state, this is the original sin
}

widget클래스는 기본적으로 immutable 즉, 한번 생성되면 state가 변하지 않습니다.

그렇기에 StatefulWidget은 StatelessWiget처럼 immutable한 위젯입니다.

그러나 반드시 statefulwidget은 state의 변화를 반영해야 합니다.

이런 문제를 해결하기 위해 두개의 클래스로 나눠 StatefulWidget인 MyApp 위젯은 immutable한 특징을 유지하고 _MyAppState 클래스는 mutable한 속성을 대신하게 만든 것입니다.

어떻게 두개의 클래스를 연결시킬 수 있을까 - 1

먼저, state 클래스의 코드입니다.

@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
  ...
}

State클래스는 제네릭 타입으로 어떤 타입이 오든지 StatefulWidget을 상속받게 하고 있습니다.

_MyAppState 클래스는 state클래스를 상속 받았고 이제, _MyAppState 클래스는 state 타입이 되었습니다.

class _MyAppState extends State<MyApp>{...}

그리고 상속 받은 state클래스의 제네릭 타입을 MyApp 클래스로 지정해준다면 이 state 클래스는 오직 MyApp타입만을 가질 수 있게 됩니다.

이를 통해 _MyAppState 클래스가 StatefulWidget인 MyApp 위젯에 연결된다고 flutter에 알려줄수 있습니다.

왜 state 클래스는 제네릭 타입을 가지게 되었을까

제네릭 타입의 장점인 코드의 재사용성, 안정성 때문입니다.

check box

Checkbox(value: false, onChanged: (value){})

check box source code

// StatefulWidget을 상속 받고 있음
class Checkbox extends StatefulWidget {
    const Checkbox({...})
    ...
}
// 상속받은 state의 제네릭 타입은 checkbox 타입으로 되어있음
class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, ToggleableStateMixin {...}

즉, state클래스를 제네릭 타입으로 만들어서 필요한 타입을 편하게 지정하고 그 외의 타입을 전달 받지 못하도록 안전장치를 해둔 것

어떻게 두개의 클래스를 연결시킬 수 있을까 - 2

이제, _MyAppState 클래스를 createState Method를 통해 MyApp 위젯 내에서 불러와야합니다.

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _MyAppState();
  }
}
class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Charactor card',
      home: MyPage(),
    );
  }
}

createState Method는 state 타입으로 지정되어 있고 제네릭 타입으로 StatefulWidget이 지정되어 있습니다.

즉, createState Method는 반드시 state 타입의 객체를 리턴해야 하는데 결국, StatefulWidget타입만이 올 수있는 객체여야 하며 이 객체는 _MyAppState 클래스에 근거하여 만들어진 것입니다.

StatefulWidget과 StatelessWidget 차이

createState Method는 StatefulWidget이 생성될 때 마다 호출되는 메서드입니다.

StatelessWidget은 build method에서 생성한 객체를 바로 반환하지만 StatefulWidget은 이 createState Method에서 생성한 객체를 반환합니다.

이제 어떻게 State를 변화시켜 랜더링 시킬까

state를 변화시킬려면 반드시 build 메서드를 호출해서 위젯을 rebuild하는 방법 뿐입니다.

단순히 버튼을 누른다고 build메서드를 호출할 수는 없습니다.

TextButton(
  onPressed: () {
      counter++;
  },
  child: Icon(Icons.add),
)

그래서, flutter는 우리 대신 build 메서드를 호출해 줄 수 있는 setState 메서드를 가지고 있습니다.

setState의 역할은 두가지 입니다.

  • 매개 변수로 전달된 함수 호출
  • build 메서드 호출
TextButton(
  onPressed: () {
    setState((){
      counter++;
    })
  },
  child: Icon(Icons.add),
)

정리

flutter는 widget tree를 기반으로 element tree를 생성합니다.

MyApp Stateful widget을 만나면 관련된 MyApp Stateful element를 추가하지만 createState 메서드를 호출해서 MyApp Stateful element와 연결된 MyAppState 객체도 생성합니다.

그런데 이 객체는 MyApp Stateful widget과 간접적으로만 연결되어 있습니다.

여기서 특이점은 MyApp Stateful element는 위젯 관련 중요 정보들을 가지고 있지만, 메모리 상에서 어디에도 종속되지 않는 독립된 객체로서 MyAppState 객체에 대한 정보도 가지게 되는 것입니다.

이제 setState메서드가 호출되고 build 메서드로 인해서 state객체가 rebuild 되면서 새로운 state를 반영한 새로운 MyApp Stateful widget이 rebuild 됩니다. 그러면 MyApp Stateful element에 연결되어 있는 MyAppState객체에 새로운 state가 저장이 되고 이제 MyAppState객체는 새롭게 rebuild된 MyAppStateful widget을 가리키게 됩니다.

왜 MyAppState 객체는 MyAppStateful widget처럼 widget tree상에서 매번 rebuild되지 않는 것일까?

비용문제

state가 변한 state 객체를 비용이 싼 Stateful widget으로 만들어서 계속 rebuild하고 MyAppState 객체는 element tree에서 mutable하게 존재하면서 필요할때마다 새로운 state를 저장함과 동시에 새롭게 rebuild된 Stateful Widget과의 링크만을 업데이트 해줍니다.

참고

https://www.youtube.com/watch?v=OvWrOKMqSG0&list=PLQt_pzi-LLfoOpp3b-pnnLXgYpiFEftLB&index=2 

 

728x90

'Study > Flutter' 카테고리의 다른 글

[Flutter] Factory 패턴  (0) 2021.07.13
[Flutter] Provider pattern (bloc -> provider )  (0) 2021.06.16
[UI초급] Container, materialApp, Scaffold  (0) 2021.06.07

+ Recent posts