관리 메뉴

Bull

[Flutter] Bloc Widget정리 본문

Software Framework/Flutter

[Flutter] Bloc Widget정리

Bull_ 2024. 7. 15. 17:58

글은 공식문서를 정리하는 형식으로 작성했습니다.

BloC Widget의 종류를 먼저 정리해보겠습니다.

BloC Widget의 종류


BlocBuilder
BlocSelector
------------------
BlocProvider
MultiBlocProvider
------------------
BlocListener
MultiBlocListener
------------------
BlocConsumer
------------------
RepositoryProvider
MultiRepositoryProvider

구분선을 나눌 이유는 없지만 Multi가 있는 Widget과 없는 Widget으로 나눴습니다.

하지만 Consumer는 Bulider와 Listener의 개념이 함께 쓰이므로 따로 나눴습니다.

 

각 BloC Widget의 설명


BlocBuilder

BlocBuilder<BlocA, BlocAState>(
  bloc: blocA, // bloc 인스턴스를 명시적 제공.
  buildWhen: (previousState, state) {
    // 빌드를 언제할 지 TRUE/FALSE를 통해 결정한다.
  },
  builder: (context, state) {
    // 빌드될 위젯.
  },
);

<Bloc과 State>를 넘겨줍니다.

 

여기서 bloc 프로퍼티는 언제 사용해야하는 지 두 예시를 통해 비교하여 보이겠습니다.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => BlocA(),
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(title: Text('Bloc Example')),
          body: BlocBuilder<BlocA, BlocAState>(
            builder: (context, state) {
              return Center(
                child: Text('State: $state'),
              );
            },
          ),
        ),
      ),
    );
  }
}

 

이는 BlocBuilder에 bloc 프로퍼티를 명시하지 않았을 때입니다.

Bloc에 대한 인스턴스를 생성하지 않았기 때문에 BlocProvider로 생성된 bloc인스턴스를 BlocBuilder는 자동으로 찾아갑니다.

class MyApp extends StatelessWidget {
  final blocA = BlocA();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Bloc Example')),
        body: BlocBuilder<BlocA, BlocAState>(
          bloc: blocA, // 명시적으로 blocA 인스턴스를 제공
          builder: (context, state) {
            return Center(
              child: Text('State: $state'),
            );
          },
        ),
      ),
    );
  }
}

반면에 bloc을 명시해주는 경우, 생성된 인스턴스가 Bloc임을 확인하여 state를 업데이트 해줄 수 있도록 프로퍼티를 제공합니다.

BlocSelector

BlocSelector<BlocA, BlocAState, SelectedState>(
  selector: (state) {
    // 상태의 특정 부분을 선택하여 반환
    return state.selectedState;
  },
  builder: (context, selectedState) {
    // 선택된 상태에 따라 위젯 빌드
    return Text('Selected state: $selectedState');
  },
);

BlocSelector는 BlocBuilder와 유사하지만 좀 더 세밀한 업데이트가 가능합니다.

Bloc의 전체 상태가 아닌 특정 부분만 구독할 수 있습니다.

 

아래 코드로 좀 더 구체적으로 확인할 수 있습니다.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => BlocA(),
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(title: Text('BlocSelector Example')),
          body: BlocSelector<BlocA, BlocAState, SelectedState>(
            selector: (state) {
              // 상태의 특정 부분 선택
              return state.selectedPart;
            },
            builder: (context, selectedState) {
              // 선택된 상태에 따라 위젯 빌드
              return Text('Selected state: $selectedState');
            },
          ),
        ),
      ),
    );
  }
}

BlocProvider

BlocProvider(
  lazy: false,
  create: (BuildContext context) => BlocA(),
  child: ChildA(),
);

BlocProvider는 BlocProvider.of<T>(context) 를 통해 child에게 bloc을 제공하는 Flutter 위젯입니다.
쉽게 얘기하자면 BlocBuilder를 자식 위젯으로 빌드할 때 bloc을 쉽게 제공할 수 있습니다.

이때 BlocProvider는 bloc을 생성하고 자동으로 close하는 일도 처리합니다.

 

여기서 lazy 프로퍼티는 BlocProvider.of<BlocA>(context)를 통해 bloc을 조회할 때 create가 실행되는데
이 동작없이 즉시 create가 생성되려면 lazy:false로 설정하면 됩니다.

BlocProvider.value(
  value: BlocProvider.of<BlocA>(context),
  child: ScreenA(),
);

BlocProvider를 사용해 기존 bloc을 새로운 route페이지에서 사용할 때 인스턴스를 전달하고자 할 때 유용하게 사용됩니다.

단 BlocProvider는 Bloc을 생성하지 않았으므로 자동으로 close하지 않습니다.

MultiBlocProvider

MultiBlocProvider(
  providers: [
    BlocProvider<BlocA>(
      create: (BuildContext context) => BlocA(),
    ),
    BlocProvider<BlocB>(
      create: (BuildContext context) => BlocB(),
    ),
    BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
    ),
  ],
  child: ChildA(),
);

MultiBlocProvider는 여러 BlocProvider 위젯을 하나로 병합하는 Flutter 위젯입니다.

BlocListener

BlocListener<BlocA, BlocAState>(
  listenWhen: (previousState, state) {
    // TRUE/FASLE를 통해 결정합니다
  listener: (context, state) {
    // 상태 변화에 따라 특정 작업 수행
    if (state is BlocASuccessState) {
      // 예를 들어, 성공 상태일 때 네비게이션
      Navigator.of(context).pushNamed('/success');
    }
  },
  child: const SizedBox(),
);

BlocListener는 bloc의 state 변경에 대한 응답으로 listener를 호출하는 위젯입니다.
Navigation, Shackbar, Dialog 등과 같이 state 변경당 한 번 발생하는 기능에 사용합니다.

 

BlocBuilder와 마찬가지로 When 프로퍼티를 제공합니다.

 

BlocBuilder와 주요 차이점

[목적]

BlocListener: 상태 변화에 반응하여 부수적인 작업(예: 네비게이션, 알림 등)을 수행합니다.
BlocBuilder: 상태 변화에 따라 UI를 빌드하거나 업데이트합니다.

 

[동작 방식]
BlocListener: 상태 변화가 발생할 때마다 listener 콜백이 호출됩니다.
BlocBuilder: 상태 변화가 발생할 때마다 builder 콜백이 호출되어 새로운 UI를 반환합니다.

 

[사용 시점]
BlocListener: 상태 변화에 따른 UI 외의 작업을 수행할 때 사용됩니다.
BlocBuilder: 상태에 따라 UI를 업데이트해야 할 때 사용됩니다.

 

 

MultiBlocListener

MultiBlocListener(
  listeners: [
    BlocListener<BlocA, BlocAState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocB, BlocBState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
    ),
  ],
  child: ChildA(),
);

MultiBlocListener는 여러 BlocListener 위젯을 하나로 병합하는 Flutter 위젯입니다.

BlocConsumer

 

BlocConsumer<BlocA, BlocAState>(
  listenWhen: (previous, current) {
	// TRUE/FALSE를 통해 결정합니다.
  },
  listener: (context, state) {
    // 상태 변화에 따라 특정 작업 수행
  },
  buildWhen: (previous, current) {
    // 빌드를 언제할 지 TRUE/FALSE를 통해 결정한다.
  },
  builder: (context, state) {
    // 빌드할 위젯.
  },
);

BlocConsumer는 Listener와 Builder의 역할을 동시에 수행할 수 있습니다.

언제 BlocConsumer를 사용할지

복합적인 작업이 필요할 때: 상태 변화에 따른 부수적인 작업과 UI 빌드를 동시에 처리해야 할 때 유용합니다.
코드의 응집성을 높일 때: 상태 변화에 대한 모든 처리를 하나의 컴포넌트에서 관리하여 코드의 응집성을 높이고자 할 때 사용합니다.

언제 BlocListener와 BlocBuilder를 분리할지

단순한 상태 구독: 특정 작업만을 수행하거나 단순히 UI를 빌드해야 할 때는 각각의 컴포넌트를 분리하여 사용하면 코드가 더 간결해질 수 있습니다.
명확한 역할 분리: 부수적인 작업과 UI 빌드의 역할을 명확히 분리하여 유지보수성을 높이고자 할 때 유용합니다.

 

RepositoryProvider

RepositoryProvider(
  create: (context) => RepositoryA(),
  child: ChildA(),
);

RepositoryProvider는 RepositoryProvider.of<T>(context)를 통해 child에게 repository을 제공하는 Flutter 위젯입니다. 

 

또한 RepositoryProvider는 Flutter 애플리케이션에서 Repositories를 제공하고 관리하는 역할을 하는 도구입니다. Repositories는 데이터 소스와의 상호작용을 담당하는 클래스입니다. 예를 들어, 네트워크 요청, 데이터베이스 쿼리, 파일 시스템 접근 등을 관리하는 역할을 합니다.

class UserRepository {
  Future<String> fetchUserName() async {
    // 데이터 소스와의 상호작용 (예: 네트워크 요청)
    await Future.delayed(Duration(seconds: 1));
    return "John Doe";
  }
}

void main() {
  runApp(
    RepositoryProvider(
      create: (context) => UserRepository(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: UserScreen(),
    );
  }
}

class UserScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final userRepository = RepositoryProvider.of<UserRepository>(context);

    return Scaffold(
      appBar: AppBar(title: Text('User Screen')),
      body: FutureBuilder<String>(
        future: userRepository.fetchUserName(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return CircularProgressIndicator();
          } else if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          } else {
            return Text('User Name: ${snapshot.data}');
          }
        },
      ),
    );
  }
}

 

MultiRepositoryProvider

MultiRepositoryProvider(
  providers: [
    RepositoryProvider<RepositoryA>(
      create: (context) => RepositoryA(),
    ),
    RepositoryProvider<RepositoryB>(
      create: (context) => RepositoryB(),
    ),
    RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
    ),
  ],
  child: ChildA(),
);

MultiRepositoryProvider는 여러 RepositoryProvider 위젯을 하나로 병합하는 Flutter 위젯입니다.


참고자료

[BloC 공식문서]

https://bloclibrary.dev/ko/flutter-bloc-concepts/

 

Flutter Bloc 핵심 컨셉

package:flutter_bloc의 핵심 개념에 대한 개요입니다.

bloclibrary.dev