[TIL] 21.04.16

패키지 [flutter_bloc]

https://bloclibrary.dev/#/coreconcepts

bloc 공식 문서에 들어가보면 두가지 패키지가 존재한다. 하나는 bloc이고 하나는 flutter_bloc이다. 이 사람의 문서나 예제를 보면 뭔가 이름들이 헷갈리게끔 짓는다는 느낌을 받는데, 이 패키지명들도 마찬가지이다…

내가 이해한 bloc과 flutter_bloc의 차이점은 bloc은 말그대로 위의 패턴에서 가운데에 있는 bloc을 구현하기 위한 패키지이고, flutter_bloc은 Flutter의 UI 영역에서 bloc 관련 내용을 반영하기 위한 위젯들을 모아놓은 패키지라는 것이다. 쉽게 말해 bloc은 BloC 영역을, flutter_bloc은 UI 영역을 위한 패키지라는 것이고 결국 둘 다 필요한 것이다!

지난번까지는 bloc 패키지를 확인했다. 이제 flutter_bloc을 알아보자.

flutter_bloc 기본

앞서 확인했던 것처럼 Bloc 영역의 클래스를 사용하기 위한 UI 영역 측의 클래스가 flutter_bloc에 정의되어 있다. 즉 Bloc 영역을 잘 만들어 이것이 이벤트를 받아 상태 변화를 처리하는 등 기능을 잘 수행하게 만들었다면 UI에서는 이 Bloc을 활용할 수 있어야 한다. Bloc의 상태를 가져와서 상태가 변화할 때마다 위젯이 다시 빌드되도록 한다던지 등등.. 이것을 위한 패키지가 flutter_bloc이라는 점을 다시 강조하고 넘어가겠다.

flutter_bloc의 위젯들

UI 영역의 클래스이기 때문에 위젯의 형태를 갖게 된다. 어떤 위젯들이 있는지 확인해보도록 하자.

BlocProvider, MultiBlocProvider

BlocProvider는 Bloc을 자식 위젯들이 접근할 수 있도록 하는 위젯이다. BlocProvider를 선언할 때 Bloc과 child를 정의하고, 그러면 child 위젯은 해당 Bloc에 접근할 수 있게 된다.

BlocProvider는 의존성 주입(DI)을 위한 위젯이라고 한다. 이게 무슨 얘기냐면, 최상위에서 BlocProvider로 Bloc을 만들어 놓으면, child 이하의 모든 위젯들이 해당 Bloc에 접근할 수 있다는 것이다. 이렇게 내부에 필요한 값(또는 객체)을 외부에서 선언해주는 것을 의존성 주입이라고 이해하면 된다. 외부에서 내부로 객체를 주입해주는 개념이다. 여기서는 BlocProvider가 내부로 Bloc을 주입해주었다. 내부에서는 그럼 Bloc을 가져다 쓰면 되는 것이다.

이를테면 아래와 같이 사용된다.

1
2
3
4
BlocProvider(
create: (BuildContext context) => BlocA(),
child: MaterialApp(),
);

위처럼 BlocProvider는 BlocA를 생성한다. 그리고 그 BlocA를 MaterialApp에 주입한다. 그러면 MaterialApp 내의 모든 위젯들이 해당 BlocA를 사용할 수 있다. 아래와 같이 말이다.

1
final _blocA = BlocProvider.of<BlocA>(context);

이렇게 BlocProvider로부터 받아온 _blocA를 어떻게 쓰는지 다음 내용(BlocBuilder)에서 살펴보고, 여러 개의 Bloc을 하위 위젯들에게 전달하기 위해서는 MultiBlocProvider를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
),
BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
),
BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
),
],
child: MaterialApp(),
)

BlocBuilder

BlocBuilder는 Bloc을 인자로 받아 위젯을 빌드하기 위한 위젯이다.

공식 문서에는 BlocProvider보다 BlocBuilder가 먼저 나오는데 설명의 순서를 바꾼 이유가 여기에 있다. 앞서 BlocProvider가 내부로 주입한 Bloc을 받아 해당 Bloc으로 위젯을 빌드해주는 위젯이 바로 BlocBuilder이기 때문에 이런 순서로 이해하는게 더 쉬울 것 같다.

1
2
3
4
5
6
7
8
9
10
final _blocA = BlocProvider.of<BlocA>(context);

...

BlocBuilder(
bloc: _blocA,
builder: (BuildContext context, BlocAState state) {
// do with state..
}
)

또는 _blocA를 따로 선언하지 않아도 BlocProvider에게 제공받을 수 있는 자식 위젯들이라면 아래와 같이 그냥 바로 사용할 수 있다.

1
2
3
4
5
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
// return widget here based on BlocA's state
}
)

StreamBuilder와 비슷한 구조를 가지고 있으며, Bloc을 인자로 받고 그 Bloc의 상태값을 활용해 위젯을 빌드하기에 상태의 변화에 따라 해당 위젯이 다시 빌드된다. 따라서 재빌드를 최소화하기 위해 상태와의 연결이 필요한 위젯만을 빌더에 넣는 것이 중요하겠다.

BlocListener, MultiBlocListener

BlocListener는 Bloc의 상태값이 변경되었을 때 호출되도록 하는 위젯이다. 설명만으로는 BlocBuilder와 유사해보이는데, BlocListener는 한 번의 상태 변화에 대해 한 번 발생해야 하는 기능(페이지 네비게이션, SnackBar나 Dialog 띄우기 등)을 위해서만 사용되어야 한다고 공식 문서에 나와있다. 살짝 미묘한 차이가 있는 것 같기는 한데 정확히 감은 오지 않으니 이는 실제로 프로젝트에 적용하면서 배워보겠다.

1
2
3
4
5
6
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)

이런 식으로 BlocBuilder와 비슷한 방식으로 사용된다. 여러 상태에 대한 BlocListener가 필요한 경우 MultiBlocListener를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MultiBlocListener(
listeners: [
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
),
BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
),
BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
),
],
child: ChildA(),
)

BlocConsumer

BlocConsumer 내용까지는 다루지 않고 넘어가보겠다.

RepositoryProvider, MultiRepositoryProvider

드디어 처음으로 BloC 영역과 관련된 위젯이 아닌 데이터 영역과 관련된 위젯이 나왔다. RepositoryProvider는 BlocProvider와 유사한 개념이다. BlocProvider는 BloC 영역의 Bloc 객체를 내부로 주입하기 위한 위젯이였고, RepositoryProvider는 데이터 영역의 Repository 객체를 내부로 주입하기 위한 위젯인 것이다. 계속적으로 주입이라는 용어를 사용하는데, 위에서 이해했던 내용을 까먹었다면 그냥 쉽게 아래와 같이 이해해보자.

최상단에서 Bloc 또는 Repository를 생성하고, 그 Bloc과 Repository를 하위 위젯들이 사용할 수 있게끔 제공해주는 역할을 해주는 것이 Provider들이다.

사용법은 BlocProvider와 비슷하다.

1
2
3
4
RepositoryProvider(
create: (context) => RepositoryA(),
child: MaterialApp(),
);

하위 위젯들이 접근할 때에도 비슷하게 사용한다.

1
2
3
4
5
// with extensions
context.read<RepositoryA>();

// without extensions
RepositoryProvider.of<RepositoryA>(context);

여러 개의 Repository를 제공해야 하는 경우 MultiRepositoryProvider를 사용한다. 역시 비슷하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MultiRepositoryProvider(
providers: [
RepositoryProvider<RepositoryA>(
create: (context) => RepositoryA(),
),
RepositoryProvider<RepositoryB>(
create: (context) => RepositoryB(),
),
RepositoryProvider<RepositoryC>(
create: (context) => RepositoryC(),
),
],
child: ChildA(),
)

마치며

드디어 bloc과 flutter_bloc의 모든 위젯들을 어느정도 만나보았다. 앞서 BloC의 구조와 개념에서 설명했듯 이는 방법이기 때문에 꼭 bloc과 flutter_bloc을 사용할 필요는 없다. 이들은 어디까지나 BloC 패턴의 구현을 도와주는 패키지들이기 때문이다. 그래도 우리는 정돈된 구조와 효율성을 위해 패키지들을 활용해 구현해보도록 하겠다.

Author

TaeBbong Kwon

Posted on

2021-04-16

Updated on

2023-01-02

Licensed under

Comments