[Flutter] BLoc 패턴 (flutter_bloc)

이상우·2022년 11월 20일
0
post-thumbnail

AndroidStudio Plugin
기본적인 bloc 코드 레이아웃을 자동으로 생성해준다.

state.dart
event.dart
bloc.dart

이 세가지 파일을 자동으로 생성해주고 기본적인 레이아웃을 자동으로 짜준다.

bloc_state.dart

  • factory 패턴을 사용해 Bloc.dart 내의 super(state를 initialized 해주는 곳) 에서 해당 factory 함수를 객체 생성 하지 않아도 바로 사용할 수 있게 하였다.
part of 'count_bloc.dart';

/// 현재 상태를 나타내는 enum
enum CountStatus { init, load, done }

/// 안드로이드 스튜디오 Bloc 플러그인이 자동으로 짜주는 코드를 약간 변형하였습니다.
/// 추상 클래스를 삭제하고
/// enum 을 생성
class CountState {
  final CountStatus status;
  final int num;
  
  CountState({required this.status, required this.num});

  /// factory 패턴 => 클라이언트에서(다른 모든 스크립트 내) 객체(생성자) 선언 안해줘도 바로 사용 가능.
  /// CountStatus 초기화
  factory CountState.initial() {
    return CountState(status: CountStatus.init, num: 0);
  }

  /// 멤버 함수
  CountState copyWith({CountStatus? status, int? num}) {
    return CountState(
        status: status ?? this.status,
        num: num ?? this.num);
  }
}

bloc_event.dart

part of 'count_bloc.dart';

///추상클래스로 event를 생성하였다.

///Equatable
/// 객체(생성자)를 선언했을 때, 생성자의 해당 메모리의 주소를 참조하여 객체를 판단하지 않는다.
/// 즉, 객체를 여러개 선언해도 똑같은 객체로 인식함.
/// 단, 기본 생성자 끼리는 같겠지만, 해당 객체의 메모리상 멤버 변수의 값이 서로 다르다면
/// 그 두 객체를 서로 다른 객체로 판단함
abstract class CountEvent extends Equatable {
  
  /// 클래스의 기본 생성자 자동으로 생성되지만 예의상 선언해줬다.
  const CountEvent();
  
  /// extends Equatable 쓸 때 필수로 있어야함.
  /// 해당 prpos 리스트 안의 값들로 hascode와 ==operator를 자동으로 생성해준다.
  /// ex) List<Object> get props => [this.멤버변수1, this.멤버변수2];
  @override
  List<Object> get props => [];
}

/// 추상 클래스를 상속
class CountPlusEvent extends CountEvent {

  /// 이벤트 호출 시 함께 받을 arg를 선언
  int num;

  CountPlusEvent({required this.num});
}

class CountMinusEvent extends CountEvent {
  int num;

  CountMinusEvent({required this.num});
}

bloc.dart

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';

part 'count_event.dart';
part 'count_state.dart';

class CountBloc extends Bloc<CountEvent, CountState> {
  /// super(이 부분) 에 state를 초기화 한다. factory로 작성한 초기화 함수여서, 객체를 따로 생성하지 않아도 된다.
  CountBloc() : super(CountState.initial()) {
    /// 이벤트를 등록시켜준다
    on<CountPlusEvent>(_countPlusEvent);
    on<CountMinusEvent>(_CountMinusEvent);
  }

  /// 등록시킨 이벤트를 정의해 준다.
  void _countPlusEvent(CountPlusEvent event, Emitter<CountState> emit) {
    int num = event.num + 1;
    
    /// emit 필수이다.
    /// emit 을 통해 변한 state를 감지한다. notifier 또는 obs 같은 것이다.
    /// emit 을 사용하지 않으면 state 가 변해도 
    /// 화면단에서 이를 감지하지 못해 화면을 다시 그리지 못 한다.
    emit(state.copyWith(status: CountStatus.done, num: num));
  }

  void _CountMinusEvent(CountMinusEvent event, Emitter<CountState> emit) {
    int num = event.num - 1;
    emit(state.copyWith(status: CountStatus.done, num: num));
  }
}

BlocProvider

  • 단일 인스턴스로 하위 여러 위젯에 제공하기 위해 사용합니다.
BlocProvider(
  create: (BuildContext context) => BlocA(),
  child: ChildA(),
);

MultiBlocProvider

  • 여러 BlocProvider 위젯을 하나로 병합하는 위젯 하위 위젯에 여러 BLoC를 동시에 제공하고자 할 때 사용합니다.

  • RxDart로 구현한 경우 한 위젯에서 여러개의 BLoC를 사용할 때
    StreamBuilder 안에 중첩으로 StreamBuilder를 쓰거나(권장되지 않는다 함)
    RxDart의 CombineLatestStream 클래스로 Stream을 병합하여 사용 하는 등
    번거로운 작업이 필요한데 해당 라이브러리의 MultiBlocProvider 클래스 등을 이용하면 간단하게 하위 위젯에서 여러개의 BLoC을 사용이 가능합니다.

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

event 호출

context.read<CountBloc>().add(CountMinusEvent(num: _counter))

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutterbloc/bloc/count_bloc.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      
      
      /// BlocProvider 를 bloc을 사용할 상위 위젯을 감싼다.
      home: BlocProvider(
        /// Bloc 등록 시키기
          create: (BuildContext context) => CountBloc(),
          child: MyHomePage(title: 'Flutter BLoc')),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      
      
      /// BlocBuilder<CountBloc, CountState> 로 
      Bloc 의 state 변화를 Stream 느낌으로 받아볼 수 있다.
      body: BlocBuilder<CountBloc, CountState>
        (builder: (context, state) {
        _counter = state.num;
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              state.status == CountStatus.init
                  ? Text(
                      'BLoc 초기화',
                      style: Theme.of(context).textTheme.headline4,
                    )
                  : Text(
                      '$_counter',
                      style: Theme.of(context).textTheme.headline4,
                    ),
            ],
          ),
        );
      }),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () =>
            
            /// event 를 호출한다.
                context.read<CountBloc>().add(CountPlusEvent(num: _counter)),
            tooltip: 'Increment',
            child: const Icon(Icons.plus_one),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () =>
                context.read<CountBloc>().add(CountMinusEvent(num: _counter)),
            tooltip: 'decrement',
            child: const Icon(Icons.exposure_minus_1),
          )
        ],
      )
    );
  }
}
profile
Flutter App Developer

1개의 댓글

comment-user-thumbnail
2023년 3월 3일

좋은 글 감사합니다.

답글 달기