State와 BuildContext의 관계 이해하기

순순·2024년 6월 9일

Flutter

목록 보기
2/16

State와 BuildContext의 관계

  • buildContext는 위젯 트리 내에서 state와 UI의 일관성을 유지하는 핵심 요소이다.
  • state는 buildContext를 통해 부모 위젯에 접근하고 데이터를 주고받는다.
  • statefulWidget 의 경우, state 는 buildContext 와 연결된다.
  • 이 연결은 영구적이다.
  • state 개체는 buildContext를 변경하지 않는다.
  • state 객체가 buildContext와 연관되어 있기 때문에 state 객체가 다른 buildContext 를 통해 직접 액세스할 수 없다는 것을 의미한다.
  • buildContext를 통해 트리의 부모 위젯에 접근하거나, InheritedWidget 데이터를 가져올 수 있다.
  • 하지만 State의 생성자에서 BuildContext를 사용하려 하면 오류가 발생할 수 있다 (생성자 호출 시 State와 BuildContext는 아직 완전히 초기화되지 않았기 때문)
  • 따라서 State 초기화 시 BuildContext 를 사용하지 않아야 한다. State의 초기화 로직은 initState에서 처리하고 BuildContext 관련 작업은 didChangeDependencies에서 수행하는 것이 좋다.

getter와 setter


getter

  • getter는 (private) 변수의 값을 안전하게 받아오는 메서드
  • 들어가선 안되는 값을 걸러줌
  • ex) age 변수는 나이를 담는 값인데 음수

setter

  • setter는 (private) 변수의 값을 안전하게 할당해주는 메서드
  • 마찬가지로 변수에 들어가선 안되는 값을 걸러줌

import 'package:flutter/material.dart';

class TabPageIndexProvider extends ChangeNotifier{

  // 눈에 보여질 화면의 순서값
  // 하단 네비게이션의 선택한 메뉴 번호
  int _currentPageIndex = 0;
  
  // currentPageIndex 라는 이름의 getter를 통해 외부에서 _currnetPageIndex 값을 읽을 수 있게 함
  // 여기서 => 는 람다 또는 단일 표현식을 정의하는 데에 사용되는 기호
  // 이 기호는 함수 본문이 단일 표현식으로만 구성되어있을 때 사용가능.

  int get currentPageIndex => _currentPageIndex;

  void setCurrentPageIndex(int index){
    _currentPageIndex = index;
    notifyListeners();
  }
}

Provider


  • flutter에서 provider는 상태관리를 단순화 시키는데 도움을 주는 패키지다.
  • 주요 역할
    • 상태관리
    • 데이터 전달 : 부모 위젯에서 자식 위젯으로 데이터 전달 가능
    • 코드 간결화
    • 변경 알림 및 UI 업데이트
      • 개발자가 코드를 통해 데이터값을 변경하면
      • provider는 notifyListener를 통해 구독중인 위젯들에게 알린다.
      • 참고로 notifyListener 메소드는 ChangeNotifier 클래스 안에 있기 때문에 상속받아서 써주면 된다.
      • 변경을 알림받은 위젯들은 자동으로 UI를 업데이트 한다.
  • create
    • ChangeNotifierProvider 가 관리할 객체를 생성하기 위해 호출하는 함수.
    • 한번만 호출되어 프로바이더가 관리할 객체를 생성한다.
    • create 함수는 보통 ‘BuildContext’ 를 매개변수로 받지만, 종종 이 context 를 사용하지 않을 때도 있다. (이럴 때 ‘_’ 를 사용하여 매개변수가 전달되지만 사용되지 않음을 나타낸다.
    • (코드 보기)
      • Counter 객체를 생성하지만 BuildContext 매개변수를 사용하지 않는 예시
        ChangeNotifierProvider(
          create: (_) => Counter(),
          child: MyApp(),
        )
      • Counter 객체도 생성하고 BuildContext 매개변수도 사용하는 예시
        ChangeNotifierProvider(
          create: (context) => Counter(context),
          child: MyApp(),
        )
        
  • 동작 과정 (흐름)
    • 생성(create) → 상태 전달(Provider.of) → 상태 변경(notifyListeners)

Consumer

  • Provider로부터 제공받은 데이터를 구독하고, 해당 데이터가 변경 될 때마다 자신의 하위 위젯들을 다시 빌드함

Provider extension methods


Provider version 4.1 부터 사용 가능한 메서드.

프로바이더에 새로운 기능을 더할 수 있게 해줌.

프로바이더 오브젝트는 위젯임. (따라서 다른 위젯에서 가능한 것은 프로바이더에서도 가능)

Context.watch

  • BuildContext의 확장 메소드
  • 일반적으로 build 메소드 내에서 사용됨.
  • 마찬가지로 provider로부터 제공받은 데이터를 구독하고, 해당 데이터가 변경될 때 마다 해당 위젯을 다시 빌드함 (= 상태변화 감지, 자동 리빌드)
  • 상태변화에 반응하여 알아서 UI 업데이트 하는게 특징

Context.read

  • BuildContext의 확장 메소드
  • 형식 context.read() → T
  • 타입 T의 오브젝트를 찾아서 그 오브젝트를 리턴해줌
    • provider.of(context, listen: false) 와 같은 역할
  • Provider로 부터 값을 읽지만, 해당 값이 변경되어도 위젯을 리빌드 하지 않음
    (= 수동 리빌드)
  • 주의)
하지만 **`context.read`**를 사용하여 읽을 수 없는 것은 다음과 같습니다:

1. Provider 트리에 존재하지 않는 객체: 만약 해당 타입의 객체가 Provider 트리에 존재하지 않으면 **`context.read`**를 사용하여 읽을 수 없습니다.
2. **`FutureProvider`**나 **`StreamProvider`**와 같은 비동기 Provider: 이러한 Provider는 비동기 작업을 수행하고 해당 결과를 제공하므로 즉시 값을 읽을 수 없습니다.
- (provider 트리 코드 예시)
    
    ```dart
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // Provider 트리의 루트에 MultiProvider를 사용하여 상태를 관리하는 Provider를 제공함
        return MultiProvider(
          providers: [
            ChangeNotifierProvider(create: (_) => Counter()), // Counter 클래스로부터 상태를 관리하는 Provider 제공
            Provider(create: (_) => UserService()), // UserService 클래스로부터 의존성을 주입하는 Provider 제공
          ],
          child: MaterialApp(
            title: 'Provider Example',
            home: MyHomePage(),
          ),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 여기서 Counter 객체를 Provider 트리에서 읽음
        final counter = context.read<Counter>();
    
        return Scaffold(
          appBar: AppBar(
            title: Text('Provider Example'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'Counter Value:',
                ),
                Text(
                  '${counter.value}', // Counter 객체의 상태를 사용하여 UI를 업데이트함
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              // FloatingActionButton 클릭 시 Counter 객체의 상태를 변경함
              counter.increment();
            },
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    
    class Counter with ChangeNotifier {
      int _value = 0;
    
      int get value => _value;
    
      void increment() {
        _value++;
        notifyListeners(); // 상태 변경을 리스너에 알림
      }
    }
    
    class UserService {
      // UserService 클래스는 사용자 정보를 가져오는데 사용됨
    }
    
    ```
    

⇒ 따라서 일반적으로 UI 업데이트가 필요한 경우 ‘watch’를 사용하고, 값만 필요한 경우 ‘read’를 사용한다.

Context.select

  • context.select<T, R>(R selector(T value)) → R
  • 네임이 변할 때만 해당 위젯을 다시 빌드 함.
  • T = Provider 로 부터 제공되는 전체 상태 객체의 타입
  • R = 선택된 상태 부분의 타입. 즉 ‘selector’ 함수가 반환하는 타입.

참고한 블로그

https://velog.io/@knh4300/Widget-이야기#widget

profile
플러터와 안드로이드를 공부합니다

0개의 댓글