이전에 Bloc에 대한 기초 정보를 알아보았으니, 더욱 깊게 알아보기 위해
기초 Bloc 사용법을 익히기 위해 Bloc, State, Event 파일로 나누어진 + , - 카운터 앱 예제를 만들어보도록 하겠습니다.
구조화와 관리를 위한 이유로 앱이 커지고 복잡해지는 경우를 위한 이유입니다.
이러한 분리된 구조를 사용하면 각 파일은 자신만의 역할을 담당하므로 앱의 로직이 명확하게 분리되며 각 역할에 맞게 구성되므로 코드의 가독성, 앱의 개발 및 유지 관리가 훨씬 쉬워집니다. 또한 다른 컴포넌트 간의 결합도를 낮추어 앱의 확장성을 향상시킬 수 있습니다.
Bloc (비즈니스 로직 컴포넌트)
Bloc 파일은 비즈니스 로직을 정의하고 상태와 이벤트를 관리합니다.
이벤트를 받아 상태를 업데이트하고, 특정 이벤트가 발생할 때 상태가 어떻게 변경되는지를 정의합니다. 이러한 역할을 통해 앱의 동작을 조율하고 데이터 흐름을 관리합니다.
State (상태)
State 파일은 앱의 현재 상태를 정의합니다.
UI에 표시되는 데이터나 화면의 상태와 관련된 정보를 정의합니다, 본 글에서 제작할 카운터 앱에서 현재 카운터 값 등을 포함할 수 있습니다.
Event (이벤트)
Event 파일은 앱에서 발생하는 이벤트나 액션을 정의합니다.
주로 사용자 동작 또는 시스템 이벤트에 관한 정보를 정의합니다, 카운터 앱에서는 "+" 버튼을 누르는 이벤트나 "-" 버튼을 누르는 이벤트를 정의할 수 있습니다.
dependencies:
flutter:
sdk: flutter
flutter_bloc:
우선, flutter_bloc
패키지를 프로젝트에 추가하기 위해 pubspec.yaml
파일에 위와 같은 코드를 추가하고 패키지를 가져옵니다.
// 이벤트 정의
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
이때 abstract Class를 만들어 일관성을 유지하고, 이후 이벤트 확장을 편리하게 만들어줍니다.
또한 기능에 따라 이벤트들이 특정 규칙을 따르도록 만들 수 있습니다.
// 상태 정의
class CounterState {
final int count;
CounterState({this.count = 0});
CounterState copyWith({
int? count,
}) {
return CounterState(
count: count ?? this.count,
);
}
}
카운트에 필요한 count 상태를 정의해 줍니다.
copyWith를 정의함으로써 불변성을 유지하면서 객체의 일부 필드를 업데이트할 수 있도록 해줍니다.
해당 메서드를 사용하여 새로운 상태를 생성할 때, 변경하려는 count
값을 전달하면 해당 필드만 변경되며 만약 count
값을 전달하지 않으면 현재의 count
값은 그대로 유지됩니다.
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_event.dart';
import 'counter_state.dart';
// 이벤트 별 동작 정의
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState()) {
on<IncrementEvent>(_handleIncrementEvent);
on<DecrementEvent>(_handleDecrementEvent);
}
void _handleIncrementEvent(
IncrementEvent event,
Emitter<CounterState> emit,
) {
print('IncrementEvent 발생');
emit(state.copyWith(count: state.count + 1));
}
void _handleDecrementEvent(
DecrementEvent event,
Emitter<CounterState> emit,
) {
print('DecrementEvent 발생');
emit(state.copyWith(count: state.count - 1));
}
}
Bloc<CounterEvent, CounterState>
각 이벤트와 상태 클래스를 상속하여 카운터 앱의 비즈니스 로직을 정의합니다.
CounterBloc() : super(CounterState())
초기 상태를 기본 초기화 상태로 적용해 줍니다.
on<IncrementEvent>(_handleIncrementEvent)
IncrementEvent
이벤트가 발생했을 때 _handleIncrementEvent
메서드를 호출하도록 등록합니다.
emit(state.copyWith(count: state.count + 1))
현재 상태를 변경하지 않고 현 상태의 카운터 값을 1 증가시킨 새로운 상태를 반환하고 새로운 상태는 UI로 전달해 카운터 값이 업데이트합니다.
Bloc 클래스의 생성자 내에서 호출되며, 이벤트와 핸들러 메서드를 연결하는 역할을 합니다.
핸들러 메서드는 이벤트가 발생했을 때 실행되며, 이벤트와 현재 상태에 따라 특정 작업을 수행하고 새로운 상태를 업데이트하는 역할을 합니다.
기본적으로 상태를 변경하지 않고 새로운 상태를 반환합니다.
해당 메서드를 호출하면 새로운 상태는 Bloc의 상태 스트림에 추가되고, 이 스트림을 구독하고 있는 위젯이나 컴포넌트가 새로운 상태를 수신하여 UI를 업데이트하는 역할을 합니다.
class BlocExerciseScreen extends StatelessWidget {
const BlocExerciseScreen({super.key});
@override
Widget build(BuildContext context) {
return ScaffoldLayout(
child: BlocProvider(
create: (context) => CounterBloc(),
child: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${state.count}',
style: const TextStyle(fontSize: 50),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
onPressed: () {
context.read<CounterBloc>().add(DecrementEvent());
},
child: const Icon(Icons.remove),
),
FloatingActionButton(
onPressed: () {
context.read<CounterBloc>().add(IncrementEvent());
},
child: const Icon(Icons.add),
),
],
)
],
);
},
),
),
),
);
}
}
BlocProvider(create: (context) => CounterBloc(), ~
CounterBloc
을 생성하고 이를 현재 화면의 context에서 사용할 수 있게 합니다.
BlocBuilder<CounterBloc, CounterState>
CounterBloc
의 상태를 감시하고, 상태가 변경될 때마다 UI를 업데이트합니다.
이때, builder
함수 내에서 현재 상태를 전달하고 UI를 빌드 합니다.
context.read<CounterBloc>().add(IncrementEvent())
IncrementEvent
를 발생시켜, CounterBloc
내에서 상태의 count 값이 증가하고 UI가 변경됩니다.
context.read<CounterBloc>().add(DecrementEvent())
DecrementEvent
를 발생시켜, CounterBloc
내에서 상태의 count 값이 감소하고 UI가 변경됩니다.
앱의 위젯 트리 중간에 Bloc 인스턴스를 제공하고 관리하기 위한 Widget
BlocProvider를 이용해 생성된 Bloc를 사용할때 쓰는 widget
해당 코드들의 전문은 Github 레파지토리에서 확인 가능합니다!
공식 사이트
하위 사이트에서 더욱 다양한 예제를 볼 수 있으니 참고하시면 좋을 듯 합니다!