# App Architecture
Complexity야말로 모든 문제의 근원이다.
# 0. Why do we need architecture(design pattern)
- 복잡성 정도에 맞은 아케텍처가 있다.
# Model View Controller Pattern
- 애플이 iOS 앱개발을 하며 상용화 했다.
- Model
- 데이터와 로직을 다룬다.
- View
- 스크린과 UI를 다룬다.
- Controller
- Model과 View 사이를 다룬다.
- 사용자 입장에서는 V -> C -> M -> C -> V 로 흘러간다.
- 카테고리별로 전문화를 할 수 있다.
# Imperative vs Reactive programming
- 뭘 할지 일일이 명령한다? imperative. 이거해라고 하면 그때부터 알아서 한다? Reactive
- 전통적으로 네이티브 앱은 Imperative Programming으로 만들었다.
- Reactive로 넘어감에 따라 Design Pattern보다 State management 차원에서 생각을 하게 된다.
- 많은 개발자들이 뭐가 좋은가에 대해 각각 강한 의견을 갖고 있다.
- Flutter만 하더라도... BloC, setState, Provider, Scoped Model, Redux, MobX, MVC, InheritedWidget과 같은 다양한 옵션이 있다. 요구에 따라 사용해야되는 패턴이 바뀌게 된다
- 구글 IO에서는 대부분의 경우
Provider
를 사용하는 것을 권장했다.
# 1. Provider
구글에서 공식 권장하는 Flutter의 state management. 그렇다고 모든 경우에 들어맞는 것은 아니다.
# 문제 정의
Lifting state up에 의하면 콜백을 계속 전달해야되는 불편함이 있다.
Widget 레벨이 깊어짐에 따라 관리가 힘들어진다. (Prop Drilling)
Level3의 데이터를 왼쪽 트리에 보내기 위해서 필요한 Prop drilling이 쓸데없이 복잡해진다. 프로그래머들은 천성이 게으르기 때문에 어마어마한 스트레스를 준다.
# 해결방안
State를 top level에 정의해주고 필요한 레벨에 notifier를 만들어 줌으로써 이를 해결할 수 있다.
# 사용방법
Provider package를 설치해준다.
최상단에 Provider를 설정해준다.
main.dart
class MyApp extends StatelessWidget{ final String data = 'data to send by provider' Widget build(BuildContext context){ return Provider<String>( builder:(context){ return data; }, child: MaterialApp( home:Scaffold( appBar:AppBar( title:MyText(data), ), body:Level1(), ) ) ) } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19data
를 설정Masterial App
위에Provider
를 최상단에 설정builder
를 넣어주고context
와data
를 넣어준다.
level3.dart
class Level3 extends StatelessWidget{
Widget build(BuildContext context){
return Text(Provider.of<String>(context))
}
}
1
2
3
4
5
6
2
3
4
5
6
- 이렇게 데이터를 prop drilling 없이 불러와서 쓸 수 있다.
- 그렇다면 이제 수정을 하고 Notifier를 통해 수정을 반영하는 방법을 알아보자
class Data extends ChangeNotifier{
//data를 notifier 안으로 갖고온다.
String data = 'Some data'
void changeString(String newString){
data = newString;
//중요. 리스너들을 부른다
notifyListeners();
}
}
class MyApp extends StatelessWidget{
Widget build(BuildContext context){
return ChangeNotifierProvider<Data>(
builder:(context){
return Data();
},
child: MaterialApp(
home:Scaffold(
appBar:AppBar(
title:MyText(data),
),
body:Level1(),
)
)
)
}
}
class Level3 extends StatelessWidget{
Widget build(BuildContext context){
//data 프로퍼티를 접근한다
return Text(Provider.of<Data>(context).data)
}
}
class MyTextField extends StatelessWidget{
Widget build(BuildContext context){
return TextField(
onChanged:(newText){
// 메소드를 불러서 state를 바꾼다.
Provider.of<Data>(context).changeString(newText);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- 일반
Provider
가 아닌ChangeNotifierProvider
로 전환후Data
를 제공해준다. Data
클래스를 정의해주고 그 내부에 값들과 setter를 정의해준다.Provider.of<Data>
를 통해서 해당 클래스에 접근하고 값들과 메소드를 사용할 수 있다.- setter 메소드 내부에
notifyListeners()
를 사용해서 리슨하고있는 애들에게 값이 변경됨을 알려준다. - 변경이 알려지면 widget이 rebuild된다.
# 그래서 어떻게 한 건데?
Inherited Widget
- Flutter가 제공하는 기본 기능
- 위젯이지만 중간레벨의 트리를 rebuild하지 않고 데이터를 전달하는 방법
- Provider는 Inherited Widget의 wrapper이다.
- Inherited Widget는 boilerplate도 많고 까다롭다.
- 이를 사용하기 편하게 해둔것이 Provider이다.
# Inherited Widget
class InheritedNose extends InheritedWidget{
final Image asset;
InheritedNose(Pthis.asset, Widget child}) : super(child:child)
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static InheritedNose of(BuildContext context)=>
context.inheritFromWidgetOfExactType(InheritedNose);
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
of
메소드를 통해 같은context
를 타고올라고 동일한type
을 가진 위젯을 찾는다.본적이 유사하다. 사실
Theme
도 InheritedWidget이다.InheritedWidget
은 Immutable하다. 그래서 값은final
이다.**값을 바꾸기 위해선 위젯을 rebuild해줘야 한다. **
← Flutter BuildContext →