flutter_bloc pub dev
https://pub.dev/packages/flutter_bloc
Equtable 코드 팩토리
https://blog.codefactory.ai/flutter/equatable/
AndroidStudio Plugin
기본적인 bloc 코드 레이아웃을 자동으로 생성해준다.
state.dart
event.dart
bloc.dart
이 세가지 파일을 자동으로 생성해주고 기본적인 레이아웃을 자동으로 짜준다.
bloc_state.dart
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),
)
],
)
);
}
}
좋은 글 감사합니다.