BLOC 패턴

Bloc 패턴은 UI사이에서 이벤트 및 데이터를 수신하고 전달하는 비즈니스 로직 패턴입니다.

기본적으로 Reactive Programming을 기본으로 사용하여 이벤트 및 데이터의 흐름을 처리합니다.

이전에 포스팅했었던 RxKotlin 과 비슷한 RxDart를 사용합니다.

Bloc은 UI객체들이 Bloc을 구독하고 있고 Bloc의 상태가 변경이되면 구독중인 UI객체들도 즉시 변경이됩니다.

위의 즉시변경을 위한 BLoC패턴의 중점인 데이터의 흐름은 Sinks와 Stream의 중심으로 구성이 되어있습니다.

Bloc_Stream

  • Widget들은 event를 Sinks를 통하여 BLoC에 송신.
  • Widget들은 BLoC의 stream으로 알림을 받음.

Let’s BLoC

BloC을 구현하는 방법은 여러가지 방법이 있었습니다.

PublishSubject를 직접 구현하여 Sinks, Stream의 과정을 모두 직접 컨트롤 하는방법과

써드파티 라이브러리를 사용하는 방법입니다. 이게 최고 편합니다

https://github.com/felangel/bloc/tree/master/packages/flutter_bloc

Bloc_FLOW

써드파티를 사용하면 Sinks, Stream의 과정중 구독과정과 상태관리 과정을 모두 스킵을 할 수 있어서 빠르게 패턴을 구축할 수 있습니다.

  • 직접 구현
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class BlocExample {
    final _repository = Repository();
    final _subject = PublishSubject<String>();
    Observable<String> get stream => _subject.stream;
    action() async {
        _subject.sink.add("hello!");
    }
    dispose() {
        _subject.close();
    }
}
  • 라이브러리 구현
1
2
3
class BlocExample extends Bloc<Event, String> {
    void action() => emit("hello!");
}
  • emit로 별도의 구현 필요없이 Sink!
  • subject의 생명주기를 직접 관리할 필요도 없어서 편리!

Example Login Bloc

BLoC패턴을 사용하여 만든 로그인의 구조는 다음과 같이 나왔습니다.

  • LoginBloc
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class LoginBloc extends Bloc<LoginEvent, LoginState> {

  LoginBloc() : super(LoginInitial()) {
    on<Login>(_login);
  }

  ApiClient client = ApiClient();

  void _login(Login event, Emitter<LoginState> emit) async {
    emit(LoginLoading());
    await client.login(event.id, event.pw).then((value) => {
      // 토큰등 인증정보 저장 로직
      emit(LoginSucceed())
    }).catchError((onError) => {
      if ((onError as DioError).response?.statusCode == 401) {
        emit(LoginFail()),
      } else {
        emit(LoginError())
      }
    });
  }
}
  • State 관리
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@immutable
abstract class LoginState {}

class LoginInitial extends LoginState {}

class LoginLoading extends LoginState {}

class LoginLoaded extends LoginState {}

class LoginSucceed extends LoginState {}

class LoginFail extends LoginState {}

class LoginError extends LoginState {}
  • event
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
abstract class LoginEvent extends Equatable {
  const LoginEvent();
}

class Login extends LoginEvent {
  const Login(this.id, this.pw);

  final String id;
  final String pw;

  @override
  List<Object> get props => [id, pw];
}

Bloc 패턴구현이 다음과 같은 조건으로 충족되었습니다.

  • BLoC에서 직접적으로 UI를 컨트롤하지 않을것.
  • 상태관리 및 비즈니스 로직만 처리할것.

위와 같이 처리가 되면 비즈니스 로직에서 상태값을 반환하게 되고, 각각 BLoC을 구독하고 있던 UI는 자동적으로 변화하게 됩니다.

이제 단순히 UI에서는 다음과 같이 구현하면 우리가 원하는 패턴이 구성이 됩니다.

  • 구독하고 있는 UI예제
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
  BlocBuilder _blocBuilder() {
    return BlocBuilder<LoginBloc, LoginState>(
      builder: (BuildContext context, LoginState state) {
        if (state is LoginLoading) {
          // 로딩시 UI제어
        } else if (state is LoginSucceed) {
          // 로그인 성공시 UI제어
        } else if (state is LoginLoaded) {
          // 로딩완료시 UI제어
        } else if (state is LoginError) {
          Fluttertoast.showToast(msg: "로그인중 오류가 발생하였습니다.");
          return const Center();
        } else if (state is LoginFail) {
          Fluttertoast.showToast(msg: "아이디 혹은 비밀번호가 틀렸습니다.");
          return const Center();
        } else {
          return const Center();
        }
      },
    );
  }

IN PROGRESS BLOC

BLoC패턴을 구현하는데 많은 일들이 있었지만 아직도 해당 패턴에 대해 알아가고 있고, 새롭게 적용해가는 단계입니다.

기존 알려진 MVVM, MVP, MVC등 많은 패턴이 있지만, 셀메이트모바일에서 새롭게 사용되는 BLoC패턴을 통해 좀더 유지보수가 편해지는 패턴이 되었으면하는 바램입니다.

  • 참고

https://github.com/ReactiveX/rxdart https://github.com/felangel/bloc/tree/master/packages/flutter_bloc https://bloclibrary.dev/#/gettingstarted