[TIL] 21.04.07

BloC 패턴

BloC(Business Logic Component) 패턴은 구글 개발자들이 추천하는, 플러터 개발에 적용되는 가장 대표적인 디자인 패턴이다.
안드로이드에서의 MVVM 패턴(Model - View - ViewModel)과 거의 동일하며 ViewModel 대신 BloC이 들어가는 구조이다.
안드로이드 개발 당시 MVVM을 적용해봤다면 금방 적응할 수 있겠지만…

Inherited Widget

BloC을 공부하려다가 Inherited Widget을 기본적으로 사용한다길래 바로 Inherited Widget으로 넘어왔다.
Inherited Widget은 가장 기본적인 형태의 전역 상태 관리 방법이다. 즉 상태를 여러 위젯이 공유할 수 있다는 말이다.
이전에 사용했던 setState()를 살펴보면, 클래스 내부의 상태를 변경할 수 있었지만 다른 클래스의 상태에 접근하거나 변경할 수 없었다.
쉽게 말해 전역 상태에 대한 관리를 할 수 없었고, 이를 보완하기 위한 방법 중 하나가 Inherited Widget이라는 것이다.
조금 쉽게 생각하면 Inherited Widget은 자식들이 공용으로 접근할 수 있는 데이터를 갖고 있는 부모와 같은 존재인 것이다.

Inherited Widget은 대략 아래와 같은 구조를 가진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// lib/MyInherited.dart
import 'package:flutter/material.dart';

class MyInherited extends InheritedWidget {
final int shareData;

const MyInherited({
Key? key,
required this.shareData,
required Widget child,
}) : super(key: key, child: child);

@override
bool updateShouldNotify(MyInherited old) {
return shareData != old.shareData;
}

static MyInherited of(BuildContext context) {
final MyInherited? result = context.dependOnInheritedWidgetOfExactType<MyInherited>();
assert(result != null, 'No ShareData found in context');
return result!;
}
}

플러터 공식 문서의 기본 구조 코드를 가져온 것이다.
구성 요소를 위에서부터 살펴보면 일단 MyInherited는 InheritedWidget을 상속받아 선언되었다.
우리가 공유할 상태는 int shareData로 선언하였다.
그 다음으로는 생성자가 나오고, Widget child를 꼭 넣어주어야 한다.
updateShouldNotify()는 InheritedWidget의 메소드로, 간단히 생각하면 부모(MyInherited)가 가진 데이터(shareData)가 바뀌었을 때 자식 위젯들을 재빌드하여 업데이트하기 위한 메소드이다.
데이터가 변경되었다면 True, 변경되지 않았다면 False이다. 업데이트를 안하겠다면 그냥 return False; 하면 된다.
마지막으로 of()를 선언하였는데, 이를 활용하면 자식 위젯들이 부모(MyInherited)의 데이터(shareData)에 .of()만으로 손쉽게 접근할 수 있다.

자식 위젯의 예시까지 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
// lib/ChildWidget.dart
import 'package:flutter/material.dart';
import 'package:flutter_study/MyInherited.dart';

class ChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final shareData = MyInherited.of(context).shareData;
return Container(
child: Text('shareData: $shareData'),
);
}
}

final shareData 부분을 보면 그냥 바로 MyInherited.of(context).shareData와 같은 방식으로 부모(MyInherited)의 데이터에 접근하는 것을 볼 수 있다.
이제 실제로 동작하게 하기 위한 마지막 처리로, 가장 root 위젯인 MyApp을 MyInherited로 감싸서 앱 전역에서 우리가 만든 MyInherited를 부모로 사용하겠다는 코드와 HomeScreen 코드를 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// lib/screen_home.dart
import 'package:flutter/material.dart';
import 'package:flutter_study/ChildWidget.dart';

class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Inherited Widget"),
),
body: Column(
children: [
Text('This is child'),
ChildWidget(),
],
),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_study/MyInherited.dart';
import 'package:flutter_study/screen_home.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MyInherited(
shareData: 777,
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomeScreen(),
),
);
}
}

MyApp에서 MyInherited로 감싸는 것을 보면 shareData에 777을 초기값으로 넣는 것을 확인할 수 있다.
이후 실행하면 아래와 같다.

이 상태에 대한 업데이트를 하려면 어떻게 해야할까? 기본적으로 Inherited Widget의 데이터는 자식이 변경할 수 없다.
그럼 이게 어떻게 상태 관리 도구라고 할 수 있겠나 싶지만, StatefulWidget이나 기타 여러 기법을 결합하면 업데이트가 가능하도록 할 수 있다.
어차피 Inherited Widget을 공부한 것은 전역에서, 부모 => 자식 방향으로 데이터를 공유하는 컨셉에 대한 이해(나중에 BloC에서 사용되는 개념이니)를 위함이었지 이를 활용해 상태 관리를 할 것은 아니니 이 정도에서 공부를 마치겠다.

살짝 다른 길로 샌 느낌이 있지만….

헷갈리기 쉬운 포인트

BloC, Provider는 모두 디자인 패턴이다! 단순히 상태 관리 패키지라고 정의하기엔 무리가 있음.
디자인 패턴인만큼 이들은 프로젝트 구조를 다루는 분야이며, 이를 쉽게 구현할 수 있도록 구현해놓은 패키지가 각각 bloc, provider이다.
상태 관리와 연관짓게 되는 것은 이들이 반응성을 보장하는 구조이기 때문에, 즉 상태의 변화를 제어할 수 있기 때문이다.

Author

TaeBbong Kwon

Posted on

2021-04-07

Updated on

2023-01-02

Licensed under

Comments