TIL) 11/25 cubit으로 상태 관리를 해봤다

100·2025년 11월 25일

TIL

목록 보기
6/11

Cubit이란

  • Bloc 라이브러리에서 제공하는 단순 상태관리 클래스
  • 메서드를 통해 상태 변경 → emit() 호출
  • UI는 Cubit의 상태 스트림을 구독해 화면을 갱신함

Cubit 기본 구조

  • State: 불변 객체, 여러 상태 클래스로 표현
  • Cubit 클래스: 초기 상태 설정 → 메서드에서 emit() 호출
  • 비동기/동기 모두 가능

UI와 Cubit 연결

  • UI → Cubit: context.read<Cubit>().method()
  • Cubit → UI: 상태 스트림 전달
  • BlocBuilder: 상태 기반으로 UI 빌드
  • BlocListener: 네비게이션·팝업 등 부수 효과 처리
  • BlocConsumer: builder + listener 동시 사용

read / watch / select

  • read(): Cubit 인스턴스 가져오기 (액션 호출용), 리빌드 없음
  • watch(): Cubit 상태 전체 구독, 리빌드 발생
  • select(): 특정 필드만 구독, 리빌드 최적화

Cubit 동작 원리

  • 단방향 흐름: UI → Cubit → State → UI
  • emit(): 새 상태를 스트림으로 내보냄
  • 상태는 반드시 새로운 객체로 생성될 것

Cubit vs Bloc

  • 공통: 단방향 데이터 흐름, 상태 기반 UI, 테스트 용이

  • 차이점

    • Cubit: 메서드 → emit, 코드량 적음, 단순 로직에 적합
    • Bloc: Event → Handler → emit, 복잡한 로직·플로우에 적합

언제 어떤 걸 쓰는가

  • Cubit: 단일 화면, API 호출 + 로딩/성공 구조 등 대부분의 케이스
  • Bloc: 상태 흐름 복잡, 다단계 이벤트, 상태머신이 필요한 기능

여기부터는 팀장님 피드백 받은 내용들


context.read / watch / select

  • flutter_bloc이 BuildContext에 추가한 확장 함수

  • read() context.read<T>()

    • Cubit/Bloc 인스턴스를 가져오기만 함
    • 상태 변화와 무관, 리빌드 없음
    • 버튼 클릭처럼 Cubit 메서드 실행할 때 사용
  • watch() context.watch<T>()

    • Cubit 상태를 구독해 상태가 바뀔 때마다 위젯 리빌드
    • 작은 위젯은 context.watch<Cubit>().state로 간단하게 사용
  • select() context.select<T, R>(selector)

    • 상태의 특정 필드만 구독
    • selector 값이 바뀌지 않는 한 리빌드 없음 → 퍼포먼스 최적화

BlocBuilder vs watch

  • BlocBuilder: 어떤 Cubit을 구독하는지 명시적, 리빌드 범위 제어에 유리
  • watch: 간단하게 상태를 읽고 싶을 때 편함
  • 둘 다 Cubit 상태에 따라 UI를 다시 그림

Repository를 Provider로 주입하자

  • Repository를 Cubit 내부에서 직접 생성하는 대신
    상위 위젯에서 Repository 인스턴스를 Provider로 만들어 하위에서 context.read<Repository>()로 가져와 사용

  • 장점

    • 재사용성 증가
    • mock 교체 쉬워 테스트 편함
    • 여러 Cubit이 동일한 Repository 공유 가능
    • 의존성 주입 구조가 명확해짐

abstract class 대신 sealed class

  • Dart 3에서 제공하는 sealed class는
    • 같은 파일 내부에서만 하위 타입 생성 가능
    • switch/case 문에서 누락된 상태가 있으면 컴파일러가 잡아줌
    • default 없이도 모든 상태를 안전하게 처리 가능
  • Bloc/Cubit의 상태(State) 선언에 가장 안전한 방식

Repository를 직접 new 하지 말고 context.read()로 주입하는 이유

  • Repository를 Cubit 안에서 직접 new 하면 Cubit이 특정 구현체에 묶여 결합도가 높아짐
  • 테스트에서 Mock Repository로 교체하기 어렵고, 화면·플로우마다 다른 Repository를 쓰는 것도 불가능해짐
  • Provider는 위젯 트리 상단에서 Repository 인스턴스를 한 번만 만들어 두고, 하위에서는 context.read<Repository>()로 받아서 사용
  • Cubit은 생성 방식이나 구현체를 몰라도 되고, “필요한 의존성을 주입받아 쓰는” 구조가 됨
  • 의존성 관리가 깔끔해지고 교체·모킹·재사용·라이프사이클 제어가 쉬워짐
  • 결론적으로 Repository는 Provider가 만들고, Cubit은 read로 받아 쓰는 구조가 정석임

go_router와 Provider 주입 시점 조절

  • go_router는 라우트 단위로 화면을 구성하는 패키지
  • 특정 플로우(회원가입, 결제 등)에서만 필요한 Cubit은 해당 라우트 내부에서 Provider로 생성
  • 장점
    • 플로우 종료 시 Cubit이 자동 dispose
    • 메모리 관리 깔끔
    • 라우트 단위로 상태 수명 주기를 통제할 수 있음
profile
멋있는 사람이 되는 게 꿈입니다

0개의 댓글