[flutter] 플러터(Flutter)에서 상태 관리를 위해 BLoC 패턴을 어떻게 구현하나요?

플러터(Flutter)는 효율적이고 확장 가능한 상태 관리를 위해 다양한 패턴을 제공합니다. 그 중에서 BLoC(Block + Logic) 패턴은 많은 개발자들에게 인기 있는 패턴 중 하나입니다. BLoC 패턴은 비즈니스 로직을 별도의 클래스에 분리하여 코드의 가독성을 높이고, 상태 변화를 쉽게 관리할 수 있도록 도와줍니다.

BLoC 패턴의 기본 개념

BLoC 패턴은 크게 세 가지 요소로 구성됩니다.

  1. 이벤트(Event): 앱의 상태 변화를 일으키는 여러가지 이벤트입니다. 사용자의 액션, 네트워크 호출, 타이머 등 다양한 이벤트가 해당됩니다.

  2. 상태(State): 앱의 현재 상태를 나타냅니다. 예를 들어, 사용자 인증 상태, 네트워크 상태, 데이터 로딩 상태 등 다양한 상태를 나타낼 수 있습니다.

  3. 비즈니스 로직(Business Logic): 이벤트와 상태 사이의 변환 로직을 담당합니다. 이벤트를 받아서 해당 이벤트에 따라 상태를 변화시키는 역할을 합니다.

BLoC 패턴의 구현 방법

BLoC 패턴을 구현하기 위해서는 몇 가지 클래스를 정의해야 합니다.

  1. BLoC 클래스: BLoC 클래스는 비즈니스 로직을 구현하는 클래스입니다. 주로 Stream을 사용하여 이벤트를 받고, Sink를 통해 상태를 내보냅니다. BLoC 클래스는 StreamSink를 노출하고, 필요한 상태 변화 로직을 구현합니다.

  2. 이벤트 클래스: 이벤트 클래스는 사용자 액션 등의 이벤트를 정의하는 클래스입니다. 주로 enum 형태로 이벤트 종류를 정의하고, 필요한 추가 데이터를 포함할 수도 있습니다.

  3. 상태 클래스: 상태 클래스는 앱의 상태를 정의하는 클래스입니다. 주로 enum 형태로 상태 종류를 정의하고, 필요한 추가 데이터를 포함할 수도 있습니다.

구현 예시를 살펴보겠습니다.

// 이벤트 클래스 정의
enum CounterEvent {
  increment,
  decrement,
}

// 상태 클래스 정의
class CounterState {
  final int count;

  CounterState(this.count);
}

// BLoC 클래스 정의
class CounterBloc {
  int _count = 0;
  final _stateStreamController = StreamController<CounterState>();
  Stream<CounterState> get stateStream => _stateStreamController.stream;

  final _eventSinkController = StreamController<CounterEvent>();
  Sink<CounterEvent> get eventSink => _eventSinkController.sink;

  CounterBloc() {
    _eventSinkController.stream.listen(_mapEventToState);
  }

  void _mapEventToState(CounterEvent event) {
    if (event == CounterEvent.increment) {
      _count++;
    } else if (event == CounterEvent.decrement) {
      _count--;
    }

    _stateStreamController.add(CounterState(_count));
  }

  void dispose() {
    _stateStreamController.close();
    _eventSinkController.close();
  }
}

위의 코드는 간단한 카운터 앱을 구현한 BLoC 클래스입니다. 이벤트에 따라 카운트 값을 증가하거나 감소시키고, 상태 스트림에 변화된 상태를 전달합니다.

이제 위에서 정의한 BLoC 클래스를 사용하여 앱에서 상태 변화를 관리할 수 있습니다. 예를 들어, 플러터의 StreamBuilder 위젯과 함께 사용하여 상태 변화를 화면에 반영할 수 있습니다.

class CounterApp extends StatelessWidget {
  final CounterBloc _counterBloc = CounterBloc();

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<CounterState>(
      stream: _counterBloc.stateStream,
      initialData: CounterState(0),
      builder: (context, snapshot) {
        return Scaffold(
          appBar: AppBar(
            title: Text('BLoC Counter'),
          ),
          body: Center(
            child: Text('Count: ${snapshot.data.count}'),
          ),
          floatingActionButton: Column(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              FloatingActionButton(
                onPressed: () => _counterBloc.eventSink.add(CounterEvent.increment),
                child: Icon(Icons.add),
              ),
              SizedBox(height: 16),
              FloatingActionButton(
                onPressed: () => _counterBloc.eventSink.add(CounterEvent.decrement),
                child: Icon(Icons.remove),
              ),
            ],
          ),
        );
      },
    );
  }

  @override
  void dispose() {
    _counterBloc.dispose();
    super.dispose();
  }
}

위의 코드에서는 StreamBuilder를 사용하여 상태 변화를 감지하고, 변경된 상태를 화면에 반영하고 있습니다. 또한, FloatingActionButton 위젯과 함께 이벤트를 발생시켜 상태 변화를 처리하고 있습니다.

이제 BLoC 패턴을 사용하여 효율적이고 확장 가능한 상태 관리를 할 수 있게 되었습니다.

정리

플러터에서 BLoC 패턴을 사용하면 앱의 상태 변화를 효과적으로 관리할 수 있습니다. BLoC 패턴은 이벤트, 상태, 비즈니스 로직을 분리하여 코드의 가독성을 향상시키고, 상태 변화를 쉽게 관리할 수 있습니다. 위의 예제 코드를 참고하여 자신의 앱에 BLoC 패턴을 적용해보세요.