본문 바로가기
Flutter

Flutter에서 BLoC 패턴을 사용한 상태 관리 방법

by 김무스비 2024. 10. 14.
728x90
반응형

Flutter는 그 유연성과 강력한 성능 덕분에 많은 개발자들 사이에서 인기를 끌고 있습니다. 하지만 복잡한 앱을 개발할 때, 다양한 상태 관리를 어떻게 할 것인지가 중요한 이슈로 떠오릅니다. 상태 관리란 UI가 변화하는 상황에 맞게 데이터를 업데이트하고 동기화하는 과정을 의미합니다.

그중에서도 BLoC (Business Logic Component) 패턴은 매우 인기가 높은 방식입니다. 이 글에서는 BLoC 패턴이 무엇인지, 왜 유용한지, 그리고 이를 사용하여 Flutter에서 상태 관리를 어떻게 하는지에 대해 설명해보겠습니다.

bloc image


BLoC 패턴이란?

BLoC 패턴은 Google이 Flutter를 위한 상태 관리 솔루션으로 권장하는 패턴 중 하나입니다. BLoC의 핵심 아이디어는 비즈니스 로직과 UI를 분리하여 앱의 유지보수를 쉽게 하고, 코드의 재사용성을 높이는 것입니다. BLoC 패턴은 RxDart와 같은 반응형 프로그래밍 방식에 기반하며, Stream을 사용하여 데이터 흐름을 제어합니다.

BLoC 패턴에서 상태와 이벤트는 Stream을 통해 전달되며, 사용자가 UI에서 발생시키는 이벤트에 따라 BLoC에서 상태를 업데이트하고 이를 다시 UI에 반영합니다. 이렇게 이벤트와 상태를 명확하게 분리함으로써, 코드의 가독성과 유지보수성이 크게 향상됩니다.

왜 BLoC 패턴을 사용해야 할까?

Flutter 앱이 간단할 때는 상태 관리가 크게 문제가 되지 않을 수 있습니다. 그러나 앱이 복잡해지면서 여러 위젯들이 서로 상호작용하거나, 화면 전환이 자주 일어날 경우 상태 관리를 체계적으로 할 필요성이 커집니다. BLoC 패턴을 사용하면 다음과 같은 장점이 있습니다.

  1. 비즈니스 로직과 UI의 분리: UI와 비즈니스 로직을 명확하게 분리하여 유지보수를 쉽게 할 수 있습니다. UI는 사용자 인터페이스를 그리는 역할만 하고, 실제 데이터 처리는 BLoC에서 이루어집니다.
  2. 재사용성 증가: 비즈니스 로직을 한 번 구현해 두면, 이를 여러 화면에서 재사용할 수 있습니다.
  3. 예측 가능한 상태: 모든 상태와 이벤트가 Stream을 통해 관리되므로, 상태 변화가 예측 가능하고, 이를 쉽게 테스트할 수 있습니다.
  4. 복잡한 애플리케이션에 적합: BLoC 패턴은 대규모 애플리케이션이나 상태가 빈번하게 변경되는 앱에서 매우 효과적입니다.

Flutter에서 BLoC 패턴 구현하기

BLoC 패턴을 이해했으니, 이제 Flutter에서 이를 어떻게 구현할 수 있는지 살펴보겠습니다. BLoC 패턴을 구현하는 기본 단계는 다음과 같습니다:

  1. 이벤트 정의: 사용자가 발생시키는 액션을 이벤트로 정의합니다.
  2. 상태 정의: UI에서 반영할 상태를 정의합니다.
  3. BLoC 클래스 작성: 이벤트를 처리하고, 상태를 업데이트하는 BLoC 클래스를 작성합니다.
  4. StreamBuilder로 UI 구성: BLoC에서 발생하는 상태 변화를 StreamBuilder 위젯을 사용해 UI에 반영합니다.

BLoC 예제 코드

간단한 카운터 앱을 BLoC 패턴으로 구현해 보겠습니다.

import 'dart:async';

// 1. 이벤트 정의
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}

// 2. 상태 정의
class CounterState {
  final int counter;
  CounterState(this.counter);
}

// 3. BLoC 클래스 작성
class CounterBloc {
  int _counter = 0;

  // 이벤트를 받을 컨트롤러 (입력)
  final _eventController = StreamController<CounterEvent>();
  Sink<CounterEvent> get eventSink => _eventController.sink;

  // 상태를 전달할 컨트롤러 (출력)
  final _stateController = StreamController<CounterState>();
  Stream<CounterState> get stateStream => _stateController.stream;

  CounterBloc() {
    // 이벤트 수신 대기
    _eventController.stream.listen(_mapEventToState);
  }

  void _mapEventToState(CounterEvent event) {
    if (event is IncrementEvent) {
      _counter++;
    } else if (event is DecrementEvent) {
      _counter--;
    }

    // 상태 업데이트
    _stateController.sink.add(CounterState(_counter));
  }

  void dispose() {
    _eventController.close();
    _stateController.close();
  }
}

이 예제에서 우리는 카운터 증가와 감소 이벤트를 처리하고, 이에 따라 상태를 업데이트합니다. 이 상태는 Stream을 통해 UI로 전달됩니다. 이제 이 BLoC를 사용하여 Flutter UI를 구성해보겠습니다.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

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

class CounterScreen extends StatefulWidget {
  @override
  _CounterScreenState createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  final CounterBloc _bloc = CounterBloc();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BLoC 패턴 카운터'),
      ),
      body: StreamBuilder<CounterState>(
        stream: _bloc.stateStream,
        initialData: CounterState(0),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return CircularProgressIndicator();
          }
          return Center(
            child: Text('카운터 값: ${snapshot.data!.counter}'),
          );
        },
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => _bloc.eventSink.add(IncrementEvent()),
            child: Icon(Icons.add),
          ),
          SizedBox(width: 10),
          FloatingActionButton(
            onPressed: () => _bloc.eventSink.add(DecrementEvent()),
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }

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

이 코드에서는 StreamBuilder를 사용하여 BLoC에서 발생한 상태 변화를 감지하고, 그에 따라 UI를 업데이트합니다. 플로팅 버튼을 통해 이벤트를 발생시키고, 해당 이벤트에 따라 카운터가 증가 또는 감소합니다.

결론

Flutter에서 BLoC 패턴은 앱의 상태 관리와 비즈니스 로직을 깔끔하게 분리할 수 있는 강력한 도구입니다.

특히, 복잡한 애플리케이션을 개발할 때는 상태 변화를 체계적으로 관리하는 것이 필수적입니다. BLoC 패턴을 통해 개발자는 더 예측 가능하고 유지보수가 쉬운 코드를 작성할 수 있으며, UI와 비즈니스 로직을 분리함으로써 재사용성을 높일 수 있습니다. Flutter에서 중대형 앱을 개발하거나 상태 관리에 고민이 있다면, BLoC 패턴을 적극적으로 활용해보시면 좋을 것 같습니다. 감사합니다.

728x90
반응형