본 문서는 Fluter Example의 내용을 원저작자의 동의하에 번역한것 입니다.
원 저작자 Eric Windmill에게 감사를 전합니다.
이해하는데 불필요한 문장은 과감하게 버렸습니다. 오 번역에 대해서 의견 주시면 적극 반영 하겠습니다.

Dissecting the Counter App (카운터앱 해부)

‘Hello World’ 플러터 앱은 다음과 같은 간단한 카운터 이다.
counter app1 counter app2

1. 터미널에서 다음 명령을 실행.

1
2
flutter create my_app
cd my_app

에디터로 project을 open.

2. IDE 또는 터미널을 사용하여 앱을 실행.

1
flutter run

IntelliJ: IDE상단에는 다음과 같은 모양의 도구 모음이 있다.(나의 경우 테마를 설치해서 아마도 여러분의 아이콘과는 다를 것이다.)
ide toolbar

  1. toolbar 이미지의 iPhoneX 라고 씌여진 부분이 비어 있는 경우 iOS 시뮬레이터 열기(open iOS Simulator)를 선택.
  2. 앱이 실행되면 'play’버튼을 클릭해서 앱을 실행. 주의 여러분은 'debug’버튼을 debug mode로 실행할 수 있다. 나는 일반적으로 개발할땐 항상 debug mode로 실행한다. debug mode 는 play버튼 실행시와 동일하지만 'breakpint’와 'inspector’를 허용한다.
    앱이 실행되면:

3. 앱의 오른쪽 하단의 ‘+’ 버튼을 클릭.

그것이 유일한 기능이고 카운터가 변경되는것을 확인해 보자.

무슨일이 일어났나? 버튼에는 카운터를 나타내는 앱 상태 변수를 증가 시시는 이벤트 리스너가 있다.
플러터는 해당 상태를 변경할 때마다 해당 상태에 의존하는 위젯(이 경우 number)를 다시 출력해야 함을 알고 있다.

4. 코드 이해하기

첫번째로 디렉토리 구조(directory structure)상에서 무슨 작업을 하는지 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
my_app
|- android
| ... a bunch of junk
|- ios
| ... a bunch of junk
|- lib
| main.dart
|- test
pubspec.lock
pubspec.yaml
README.md
...

개발시간의 99%는 lib폴더와 pubspec.yaml 파일에만 관심이 있으며, 여기서 프로젝트 의존성을 나열 할 수 있다.
lib폴더는 앱을 빌드하는 곳이며, my_app프로젝트에는 main.dart만 존재한다.
main.dart는 반드시 존재해야 하며, 반드시 lib폴더 root에 존재해야 한다. 이 파일은 다트와 플러터가 진입점으로 인식하는 파일 이다.

파일의 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// import the Flutter sdk
import 'package:flutter/material.dart';

// 모든 다트 프로그램은 무조건 main()함수가 있어야 한다.
// 이 함수는 본질적으로 자바스크립트 Document.ready()이며, 반드시 있어야 한다.
// 이것은 다트 코드의 진입점이다.
// runApp은 앱을 실행하는 플러터 함수 이다.
// 이것은(runApp함수) 위젯을 인수로 취한다.
void main() => runApp(new MyApp());

// 모든것이 위젯이다.
// 전체앱의 root를 포함:
class MyApp extends Stateless Widget {
...

// 주의: MyApp은 임의의 이름 이다.

Stateless and StatefulWidgets

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// Stateless 위젯은 어떠한 상태변경도 하지 않는다.
// 여러분 어플리케이션의 Root
class MyApp extends StatelessWidget {
// Build method
@override
Widget build(BuildContext context) {
// MaterialApp은 플러터의 기본내장된 위젯으로 다양한 스타일을 제공한다.
// MaterialApp의 가장 중요한 아규먼트는 title과 home이다.
return new MaterialApp(
// Arguments that Material App is looking for.
title: 'Flutter Demo',
theme: new ThemeData(
// 이것은 앱의 기본색상을 Blue로 설정한다.
primarySwatch: Colors.blue,
),
// MyHomePage은 개발자인 여러분이 이름붙인 또 다른 임의의 위젯이다.
home: new MyHomePage(title: 'Flutter Home Demo Page'),
);
}
}


// 이 위젯은 카운터의 상태를 관리하고 있으므로 Statefull임.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// 제목으로 전달되고 있어 위에서 볼수 있다.
final String title; // => Flutter Home Demo Page

// Stateful 위젯은 build메서드를 가지고 있지 않다.
// createState() 메서드를 가지고 있고 State를 생성하면 플러터 State클래스를 상속한 클래스가 리턴된다.
@override
_MyHomePageState createState() => new _MyHomePageState();

// Stateful Widgets are rarely more complicated than this.
}


// 이것은 MyHomePage가 생성한 state이다.
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;

// flutter counterApp sample의 comment가 내가 설명하는것보다 더 나아서 남겨둠.
void _incrementCounter() {
// 플러터 메서드에 내장
setState(() {
// setState의 호출이 발생하면 플러터 프레임워크에 뭔가 상태 변경이 있음을 알려주며, 이로 인해 아래의
// build 메서드가 재실행되어 디스플레이에 업데이트된 값이 반영 될 수 있다.
// setState()를 호출하지 않고 _counter의 값을 변경하면 build메서드가 다시 호출되지 않으므로 아무 일도 일어나지 않을 것이다.
_counter++;
});
}

@override
Widget build(BuildContext context) {
// 이 메서드는 setState가 호출되면 매번 재 실행된다. (예를 들어 위의 _incrementCounter메서드가 완료되면)

// Scaffold는 우리에게 표준 모바일 앱 레이아웃을 제공하는 앱의 또 다른 빌드다.
return new Scaffold(
//앱의 상단에 있는 bar
appBar: new AppBar(
// State클래는 widget.someProperty 형식으로 부모 클래스의 프로퍼티를 액세스 한다.
// StatefulWidgets와 해당 StateClasses를 단일 위젯으로 생각하는 것이 더 쉽다.
title: new Text(widget.title),
),
body: new Center(
// Center는 layout위젯이다. 이것은 단일 자식을 취하며 부모의 중간(한가운데)에 위치 시킨다.
child: new Column(
// Column 또한 layout위젯이다. 이것은 다수의 자식을 수직으로 정렬한다.
// 기본적으로 자식들을 수평으로 맞추기 위해 크기를 정하고 부모의 사이즈만큼 커진다.(역자주: 부모의 사이즈를 자식수로 나눠서 사이즈가 정해진다.)
//
// Column은 크기를 어떻게 해야 하고 자식들을 어떻게 위치시키는지를 제어하는 다양한 속성을 가지고 있다.
// 여기서 우리는 mainAxisAlignment를 사용하여 자식들을 세로 중앙에 위치 시킨다.
// 여기서 주축은 세로축이다, Columns이 수직이기 때문이다.(교차축은 수평이 된다)
//
// mainAxisAlignment와 crossAxisAlignment는 CSS의 Flexbox또는 Grid를 사용해봤다면 매우 익숙할 것이다.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new Text(
// Text는 첫번째 아규먼트 String을 이용한다.
// 우리는 counter의 값을 삽입 String으로 전달한다.
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
// Floating actino버튼은 특수 버튼임.
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
}

The Widget Tree

이 모든 클래스의 중첩이 혼란 스러울 경우 위젯 시각화된 트리가 도움이 될 것이다. 다음 그림은 여러분의 현재 Counter 어플리케이션의 위젯 트리이다. 중첩된 위젯은 단순히 모든 JS기능이 내장된 중첩된 React컴포넌트 또는 HTML웹 구성요소로 생각할 수 있다.

widget tree

Comment and share

크리에이티브 커먼즈 라이선스
이 저작물은 크리에이티브 커먼즈 저작자표시-비영리-변경금지 2.0 대한민국 라이선스에 따라 이용할 수 있습니다.

본 문서는 Fluter Example의 내용을 원저작자의 동의하에 번역한것 입니다.
원 저작자 Eric Windmill에게 감사를 전합니다.
이해하는데 불필요한 문장은 과감하게 버렸습니다. 오 번역에 대해서 의견 주시면 적극 반영 하겠습니다.

BuildContext Class

모든 플러터 위젯은 BuildContext 인수(매개변수)가 포함된 @override build() 메소드를 가지고 있다.

1
2
3
4
5
class CartItemWidget extends StatelessWidget {

@override
Widget build(BuildContext context) {
// ...

why BuildContext

BuildContext를 간단히 설명하자면 다음과 같다:

  • 위젯tree에서 위젯의 위치.
  • 중첩되어 감싸진 위젯의 위젯. <div <div> .html>와 같은…
  • qt와 비슷한 부모 객체들(parent objects in qt and alike) (역자주: 여기서 말하는 qt가 뭔지 모르겠음.)
  • 플러터에선 최종 build.call() 까지 모든게 위젯임.
  • 마지막으로 위젯이 "stuff"를 리턴 할때 까지의 행(row) 차원(dimentions).

이해 해야할 중요한 개념은:

  1. 모든 위젯은 고유의 build() 메소드와 context를 가지고 있다.
  2. BuildContextbuild() 메소드에 의해 리턴된 위젯의 부모다.

즉, 위젯의 build() 메소드를 호출하는 위젯의BuildContextbuild()를 통해 리턴되는 위젯의 BuildContext는 동일하지 않다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState() {
print(context.hashCode);
// prints 2011
}

@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Container(),
floatingActionButton:
new FloatingActionButton(onPressed: () => print(context.hashCode)),
// prints 63
);
}
}

(역자주 : 위 코드에서 두개의 print 메소드 실행시 전달된 BuildContext의 hashCode값이 다르다는것은 서로 다른 객체라는걸 알수 있다.)

그래서 뭘 얘기 하려는 거요?
이거 큰거 하나 잡았다!(큰거 하나 알게되었다는 뜻인듯…) ;-)

  • 잘못된 build()를 참조하기 쉽다.
  • 그리고 그것은 context. (역자주: 잘못된 build()의 리턴값인 BuildContext를 참조하여 문제를 일으 킬수 있다정도로 이해됨 )
  • 이것은 특히 of() 메소드를 사용할때 예기치 않은 상황이 발생 할 수 있다.

The ‘of()’ Method

플러터에선 모든게 위젯이므로,(역자주: 이제 집겨다!!) 어떤 경우 다른 위젯을 참조하기 위해 위젯 트리를 위/아래로 훑기도 한다. 이것은 일부 기능에선 필수이다.

특히 상속(inherited)된 위젯의 상태를 사용하려는 위젯은 상속한 위젯을 참조 할 수 있어야 한다. 이것은 일반적으로 of() 메소드의 형식으로 제공된다.

예 :

1
2
3
4
5
6
@override
Widget build(context) {
return new Text('Hello, World',
style: new TextStyle(color: Theme.of(context).primaryColor),
);
}

of() 메소드는 내부적으로 Theme 유형의 다음 상위 위젯에 대한 트리를 찾고 기본 색상 속성을 가져온다. 프레임워크는 이 build context와 관련한 트리를 알고 있으므로 올바른 Theme개체를 찾을 수 있다.

The Gotcha

플러터는 scaffold(비계)를 통해 우리에게 다음과 같은 문제를 해결 할 수 있는 좋은 방법을 제공한다.

snackbar와 같은 일부 위젯을 만들때, 가장 근접한 Scafold 컨텍스트를 얻어서 플러터가 snackbar를 그리는 방법을 알게 해야 한다. Scafold는 실제 우리가 snackbar를 디스플레이 할 수 있는 위젲이기 때문이다.

아래 코드는 작동하지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Container(),
/// 이 컨텍스트는 Scaffold가 없음
/// 왜냐면 빌드로 전달된 컨텍스트는 현재 트리의 상위 위젯이고
/// 상위 위젯에에는 Scaffold가 없기 때문임.
///
/// 이것은 에러를 던질것이다:
/// 'Scaffold.of() 호출시 전달된 context에는 Scaffold가 포함되어 있지 않기 때문에'
floatingActionButton: new FloatingActionButton(onPressed: () {
Scaffold.of(context).showSnackBar(
new SnackBar(
content: new Text('SnackBar'),
),
);
}));
}

Builder Methods

Builderclosure를 사용하여 하위 위젯을 작성하는 위젯이다. laymans(역자주: In laymans - 평신도 라는 단어인데 어떻게 해석할지 모르겠음)에서는 build 메소드로 리턴되는 자식들에게 직접 컨텍스트를 전달하는데 사용 할 수 있다.

위의 예제를 사용하면(역자주: 위에 오류난 코드를 사용):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Container(),
/// Builders를 사용하면 현재 build메소드에서 컨텍스트를 전달할 수 있다.
/// 이 build 메소드에서 리턴되는 하위 위젯에 직접 전달.
// 'builder' 속성은 모든 위젯에서 build메소드로 정확하게 처리 할 수 있는 callback를 허용.
floatingActionButton: new Builder(builder: (context) {
return new FloatingActionButton(onPressed: () {
Scaffold.of(context).showSnackBar(
new SnackBar(
backgroundColor: Colors.blue,
content: new Text('SnackBar'),
),
);
});
}),
);
}

tip: 여러분은 단순히 build메소드를 작게 만들고 더 상위 위젯에서 Scaffold를 리턴하여 이 문제를 해결 할 수도 있다. 의심 스러운 경우 더 작은 리턴 method를 고수하라!

Comment and share

크리에이티브 커먼즈 라이선스
이 저작물은 크리에이티브 커먼즈 저작자표시-비영리-변경금지 2.0 대한민국 라이선스에 따라 이용할 수 있습니다.
  • page 1 of 1
Author's picture

Jace Shim


Seoul, Korea