[fluuter] 상태 관리 - Bloc

Shin·2022년 11월 14일
0

flutter

목록 보기
2/6

상태는 앱 내에서 다뤄지는, 변화되는 데이터이다
상태는 어떤 이벤트로 인해 변화할 수 있고, 이런 이벤트와 상태를 관리해, 상태 전반에 필요한 내용을 관리하는 것을 상태 관리라고 한다

그동안 StatuefulWidget와 setState()를 활용하여 상태를 관리하였다
하지만 다른 화면으로 이동했을 때, 상태가 유지되지 않는다는 한계가 있다

따라서 앱 전역에서 관리할 상태를 구현하기 위한 패키지가 존재한다

Bloc

Bloc는 Business Login Components의 약자이다
Bloc를 사용하기 전, 디자인 패턴에 대해 간단히 알아보자면, 디자인 패턴은 프로젝트 설계에 사용되는 일반적인 방법을 의미한다

기존 iOS나 안드로이드에선 주로 MVC, MVVM 패턴을 사용했다
간단히, Model과 화면을 나타내는 View로 구분하고, 그 사이에서 Controller 또는 ViewModel이 둘 사이의 통신을 담당해 화면에 데이터를 나타내는 방법이다

플러터에서 이 Controller 역할을 해주기 위한 객체이자, MVC 패턴과 비슷하게 알려진 패턴이 바로 Bloc이다
따라서 Bloc은 패턴의 이름이기도, Controller 역할군의 이름이기도 하다

Bloc 패턴은 데이터 영역, 화면 영역, 그리고 Bloc 영역으로 구성된다
그런데 이게 상태 관리와 무슨 관련이 있을까?

상태는 기본적으로 변화하는 데이터이고, 따라서 데이터 영역에서 다룰 수 있다
그리고 이것이 화면 영역으러 전달되는데, 그 사이에 Bloc가 있기 때문에 결국 Bloc이 상태를 받아와 화면에 전달하거나, 상태의 변화를 발생시키는 등 상태 관리를 맡는다

우리가 원하는 상태의 관리는 실시간으로 앱 전역에 있는 상태의 변화를 화면 영역이 알 수 있어야 하며, Bloc 패턴에서는 이를 구현하기 위해 Stream이라는 컨셉을 활용한다
Stream을 통해 전달된 이벤트들은 화면 영역에 상태 변화를 알려주게 된다

bloc 패키지

# 패키지 추가
dependencies:
  flutter:
   sdk: flutter
  bloc: ^
  flutter_bloc: ^

bloc와 flutter_bloc의 역할은 다르기 때문에 같이 설치한다
가운데 bloc영역은 state와 event로 구성되어 있다
bloc은 state를 UI에 전달하고, UI는 event를 발생시켜 bloc에 전달한다
bloc는 stream을 통해 앱 전역에 공유할 수 있다

1. bloc 생성

프로젝트 폴더에 blocs/counter를 만든다
counter 폴더를 오른쪽 마우스 선택 후 Bloc Class를 선택 후 이름은 counter로 지정해준다
다음과 같이 파일 3개가 생성된다

2. state

counter_state.dart에선 Counter라는 Bloc가 다를 상태에 대해 정의한다

part of 'counter_bloc.dart';


class CounterState{
  final int count;
  const CounterState(this.count);
}

3. event

event는 Bloc에 입력으로 들어갈 이벤트를 정의하는 곳이다
여기서 정의하는 이벤트는 동작을 수행하는 함수가 아니라, 어떤 종류의 이벤트인지 그 이름을 정의하는 곳이므로 목록만 작성해 놓는다

part of 'counter_bloc.dart';


abstract class CounterEvent {
  const CounterEvent();
}

class CounterIncrement extends CounterEvent{
  const CounterIncrement();

  
  String toString() => '[+] CounterIncrement';
}

class CounterDecrement extends CounterEvent{
  const CounterDecrement();

  
  String toString() => '[-] CounterDecrement';
}
  1. bloc
    bloc는 앞서 정의한 state와 event를 기반으로 실제 로직을 처리하는 곳이다
    bloc에서는 실제로 어떤 event에 대해 어떤 state 변화를 발생시키는지 작성하게 되며, Stream 문법이 적용된다
import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

part 'counter_event.dart';
part 'counter_state.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0));

  
  Stream<CounterState> mapEventToState(
      CounterEvent event,
      ) async* { 						// async*는 Stream을 반환
    if(event is CounterIncrement){
      yield CounterState(state.count + 1);
    } else if(event is CounterDecrement){
      yield CounterState(state.count - 1);
    }
  }
}

Stream<CounterState> mapEventToState()는 bloc의 기본 메소드로, 정의한 CounterState를 Stream 형태로 제고하기 위한 메소드이다
event를 state의 변화로 바꿔주기 위한 메소드이므로, CounterEvent가 인자로 들어온다


flutter_bloc 패키지

위에 정의한 bloc을 UI 영역과 연결하기 위해 사용하는 패키지이다
flutter_bloc 패키지에서는 bloc를 제공받아 UI 내 위젯을 빌드하고 bloc 객체를 사용할 수 있도록 해주는 여러 위젯이 존재한다

1. BlocProvider

먼저 앱 전역에 우리가 정의한 CounterBloc를 제공해야 한다
이렇게 상위 위젯에서 객체를 생성하고, 하위에서 제공받아 사용하는 것을 Provider 패턴이라고 한다
앱 전역에서 사용할 객체를 하나만 생성해두면 모든 하위 위젯에서 사용할 수 있기 때문에 매번 생성하지 않아도 되고, 내용도 공유 가능하다

flutter_bloc에는 bloc를 앱 전역에 제공하기 위한 위젯은 BlocProvider가 있다
이를 활용해 CounterBloc를 앱 전역에 선언하고, 하위 위젯이 이를 제공받아 사용한다

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_state_bloc/blocs/counter/counter_bloc.dart';
import 'package:flutter_state_bloc/screens/screen_counter.dart';
import 'package:flutter_state_bloc/screens/screen_home.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return BlocProvider(
        create: (context) => CounterBloc(),
      child: MaterialApp(
        title: 'Flutter State BloC',
        routes: {
          '/': (context) => HomeScreen(),
          '/counter': (context) => CounterScreen(),
        },
        initialRoute: '/',
      ),
    );
  }
}

2. BlocBuilder

flutter_bloc에서는 BlocBuilder라는 위젯을 제공하여 Bloc의 변화에 따라 위젯을 빌드할 수 있다

body: BlocBuilder<CounterBloc, CounterState>(
   buildWhen: (previous, current) => previous.count != current.count,
   builder: (context, state){
    return Center(
       child: Column(
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
           Text('Count : ' + state.count.toString()),
           TextButton(
               onPressed: (){
                 Navigator.of(context).pushNamed('/counter');
               },
               child: Text('Go to CounterScreen'),
           ),
        ],
      ),
    );
  },
)

bulidWhen 옵션을 토해 언제 빌드할지 설정할 수 있다
기본적으로 상태의 변화가 발생하면 빌드하지만, 특수한 경우에 조건문 혹은 True/False 값을 설정해 빌드에 대한 설정을 할 수 있다

상태와 관련된 값을 사용하려면 builder의 (context, state) 중 state를 통해 상태 값을 사용할 수 있다

2. BlocProvider.of(context)

이벤트를 발생시키기 위해선 state가 아닌 Bloc 객체에 접근할 수 있어야 한다
이를 위해 BlocProvider.of(context) 문법을 사용한다

body: BlocBuilder<CounterBloc, CounterState>(
   builder: (context, state){
     return Center(
       child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
         children: [
          Text('Counter : ' + state.count.toString()),
          TextButton(
            onPressed: (){
              BlocProvider.of<CounterBloc>(context).add(CounterIncrement());
            },
             child: Text('[+] Increment'),
          ),
           TextButton(
             onPressed: (){
               BlocProvider.of<CounterBloc>(context).add(CounterDecrement());
            },
             child: Text('[-] Decrement'),
           ),
         ],
       ),
     );
   },
),

버튼이 눌렸을 때 BlocProvider.of<CounterBloc>(context).add()를 통해 이벤트를 Bloc 객체로 전달할 수 있다

요약

  1. state : 앱 내에서 변화하는 데이터
  2. event : 상태의 변화를 발생시키는 모든 요소들
  3. bloc : event를 입력받아 state의 변화를 발생시키는 로직 처리 객체
  4. BlocProvider : bloc 객체를 상위 위젯으로 선언하여 하위 위젯들에게 제공하기 위한 위젯
  5. BlocBuilder : 상위 위젯에서 선언된 bloc 객체를 가져와 하위 위젯에서 화면 빌드 시 활용하기 위한 위젯

Bloc 방법은 코드 간 의존성이 낮다면, 내가 작업하고 있는 파일이 다른 사람들에게 영향을 거의 주지 않기 때문에 협업에선 유용할 수 있다

하지만 비교적 많은 역할군과 파일이 생겨나고, 배우기 어렵다는 단점이 있다


출처 : 쉽고 빠른 플러터 앱 개발

0개의 댓글