본 문서는 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