# 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)

    image-20220205152809468

  • Level3의 데이터를 왼쪽 트리에 보내기 위해서 필요한 Prop drilling이 쓸데없이 복잡해진다. 프로그래머들은 천성이 게으르기 때문에 어마어마한 스트레스를 준다.

# 해결방안

  • State를 top level에 정의해주고 필요한 레벨에 notifier를 만들어 줌으로써 이를 해결할 수 있다.

    image-20220205153207438

# 사용방법

  • 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
    19
    • data를 설정
    • Masterial App 위에 Provider를 최상단에 설정
    • builder를 넣어주고 contextdata를 넣어준다.

level3.dart

class Level3 extends StatelessWidget{
    
    Widget build(BuildContext context){
        return Text(Provider.of<String>(context))
    }
}
1
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
  • 일반 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

image-20220205162757926

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
  • of 메소드를 통해 같은 context를 타고올라고 동일한 type을 가진 위젯을 찾는다.

  • 본적이 유사하다. 사실 Theme도 InheritedWidget이다.

  • InheritedWidget은 Immutable하다. 그래서 값은 final이다.

  • **값을 바꾸기 위해선 위젯을 rebuild해줘야 한다. **