Routing 2: Add New Dog Page (강아지 등록 페이지 추가)

우리가 만들 페이지는 강아지를 추가하는 페이지이다.
다음 섹션에서 사용자 입력을 처리하는 방법을 보여주겠지만, 지금 해당 경로를 추가하는 것이 좋다.

1. Add NewDogPage

lib폴더에 new_dog_form.dart라는 파일을 신규로 생성한다.

이 페이지의 UI는 간단하다:
newdogpage

다음은 기능이 없는 코드이다. (다음 섹션에서 사용자 입력 기능을 추가할 예정이다)

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
// new_dog_form.dart

import 'package:flutter/material.dart';

class AddDogFormPage extends StatefulWidget {
@override
_AddDogFormPageState createState() => _AddDogFormPageState();
}

class _AddDogFormPageState extends State<AddDogFormPage> {
@override
Widget build(BuildContext context) {
// 새로운 페이지는 scaffolding이 필요하다.
return Scaffold(
appBar: AppBar(
title: Text('Add a new Dog'),
backgroundColor: Colors.black87,
),
body: Container(
color: Colors.black54,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 32.0,
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
// Text Field는 플러터의 기본 입력 위젯이다.
// 이것은 아래에서 볼 수 있는 labelText와 같은 훌륭한UI 및 기능이 내장되어 있다.
child: TextField(
decoration: InputDecoration(
labelText: 'Name the Pup',
)),
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: TextField(
decoration: InputDecoration(
labelText: "Pup's location",
)),
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: TextField(
decoration: InputDecoration(
labelText: 'All about the pup',
),
),
),
// A Strange situation.
// 이상한 현상.
// 여러분이 다음 섹션에서 추가해야할 앱의 일부는 context를 알아야 하며,
// context를 전달하는 가장 쉬운 방법은 builder 메서드를 사용하는 것이다.
// 그래서 나는 이 button을 Builder에 일종의 'hack'으로 감쌌다.
Padding(
padding: const EdgeInsets.all(16.0),
child: Builder(
builder: (context) {
// 기본 Material 디자인 액션 버튼
return RaisedButton(
// 만약 onPressed가 null이면 button 은 비활성화(disabled)된다.
// 이건 필자가 만든 임시 callback 이다.
onPressed: () => print('PRESSED'),
color: Colors.indigoAccent,
child: Text('Submit Pup'),
);
},
),
),
],
),
),
),
);
}
}

2. Add the Routing

마지막 섹션과 마찬가지로 등록 페이지는 아직 액세스 할 수 없다. 버튼과 라우팅 정보를 _MyHomePageState 클래스에 추가하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// main.dart

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.black87,
// Material appBar의 오른쪽 상단에 새로운 버튼을 추가하는 방법이다.
// 여러분이 원하는 만큼 추가 할 수 있다.
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: _showNewDogForm,
),
],
),
...

그러면 앱의 오른쪽 상단 모서리에 플러스 기호 버튼이 추가되고 새로운 경로(route) 를 빌드 하는 메서드를 추가 할 수 있다.

main.dartnew_dog_form.dart import.

1
2
3
4
5
6
7
// main.dart

import 'package:flutter/material.dart';

import 'dog_list.dart';
import 'dog_model.dart';
import 'new_dog_form.dart';

아래 코드를 _MyHomePageState 클래스 아무곳에나 추가하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 새로운 경로를 pushing하고 그 경로가 여러분에게 되돌아 오기를 기대할때마다 여러분은 비동기 함수(async funtion)를 사용해야 한다.
// 이 경우 함수는 사용자가 작성하고 제출 할 수 있는 양식 페이지를 만든다.
// 제출시(submission) 해당 페이지의 정보가 이 함수로 다시 전달 된다.
Future _showNewDogForm() async {
// push a new route like you did in the last section
Dog newDog = await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return AddDogFormPage();
},
),
);

// 사용자가 양식을 입력했는지를 확인하기 위한 null 검사.
if (newDog != null) {
// 새로운 강아지 정보를 강아지 목록에 추가.
initialDoggos.add(newDog);
}
}

Comment and share

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

Routing 1: Pages on the Fly (페이지 이동)

플러터 앱에 페이지를 추가하는 방법에는 몇가지 있다.

여러분이 알고 있는 모든 페이지에 대해, 여러분은 선언된 경로를 사용 할 수 있다.

하지만 이 앱은 정확히 강아지 한마리당 하나씩이 페이지를 만들것이고 이는 경로 작성자에게 좋은 예(case) 이다.

1. Create a Dog Detail Page: (강아지 상세페이지 만들기)

dog_detail_page.dart라는 신규 파일을 생성하자.

이것은 StatefulWidget이 될것이고 앱 사용자는 나중에 강아지를 평가 하게 할거지만 현재로선 관리할 상태(state)가 없다.

아래 그림은 당분간 여러분이 만들려고 하는 것이다.
dog detail page

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// dog_detail_page.dart

import 'package:flutter/material.dart';

import 'dog_model.dart';

class DogDetailPage extends StatefulWidget {
final Dog dog;

DogDetailPage(this.dog);

@override
_DogDetailPageState createState() => _DogDetailPageState();
}

class _DogDetailPageState extends State<DogDetailPage> {
// 스타일에 대한 임의의 사이즈 선택
final double dogAvatarSize = 150.0;

Widget get dogImage {
// Container는 자식들의 사이즈를 정의
return Container(
height: dogAvatarSize,
width: dogAvatarSize,
// Box Decoration을 사용하여 이미지를 원형으로 만들고, 스타일을 위해 임의의 그림자(shadow)를 추가한다.
decoration: BoxDecoration(
shape: BoxShape.circle,
// CSS에서 처럼 여러분은 종종 오른쪽 모양을 위해 다수의 BoxShadows를 추가하길 원한다.
// 그래서 boxShadow 속성은 BoxShadows의 목록을 가진다.
boxShadow: [
const BoxShadow(
// CSS 처럼:
// 이것은 동일한 4개의 속성을 가진다.
offset: const Offset(1.0, 2.0),
blurRadius: 2.0,
spreadRadius: -1.0,
color: const Color(0x33000000)),
const BoxShadow(
offset: const Offset(2.0, 1.0),
blurRadius: 3.0,
spreadRadius: 0.0,
color: const Color(0x24000000)),
const BoxShadow(
offset: const Offset(3.0, 1.0),
blurRadius: 4.0,
spreadRadius: 2.0,
color: const Color(0x1F000000)),
],
// 컨테이너 배경에 이미지를 추가.
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(widget.dog.imageUrl),
),
),
);
}

// ★ 10/10으로 표시되는 등급 부분
Widget get rating {
// 위젯을 수평으로 배치하기 위해 row를 사용
return Row(
// 위젯을 행(row)의 가로축 중심에 배치
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.star,
size: 40.0,
),
Text(' ${widget.dog.rating} / 10',
style: Theme.of(context).textTheme.display2),
],
);
}

// 이미지, 평가점수, 강아지 정보를 표시하는 위젯
Widget get dogProfile {
return Container(
padding: EdgeInsets.symmetric(vertical: 32.0),
decoration: BoxDecoration(
// 이것은 앱 전체에 공유 할 수 있는 커스텀 LinearGradient 위젯을 생성 할 수 있는 좋은 기회다
// 하지만 난 이걸 당신에게 맡길것이다. (재사용 가능한 컴포넌트 관점임)
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
stops: [0.1, 0.5, 0.7, 0.9],
colors: [
Colors.indigo[800],
Colors.indigo[700],
Colors.indigo[600],
Colors.indigo[400],
],
),
),
// 강아지 프로필 정보
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
dogImage,
Text(
'${widget.dog.name} 🎾',
style: TextStyle(fontSize: 32.0),
),
Text(
widget.dog.location,
style: TextStyle(fontSize: 20.0),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 32.0, vertical: 16.0),
child: Text(widget.dog.description),
),
rating
],
),
);
}

// 마지막으로 빌드 메서드:
// 하나의 거대한 빌드 메서드를 작성하는것 보다 이 파일의 구현처럼 위젯을 분해해서 UI를 만드는것이 훨씬 쉽다.
@override
Widget build(BuildContext context) {
// This is a new page, so you need a new Scaffold!
return Scaffold(
backgroundColor: Colors.black87,
appBar: AppBar(
backgroundColor: Colors.black87,
title: Text('Meet ${widget.dog.name}'),
),
body: dogProfile,
);
}
}

2. Add the Routing mechanism: (라우팅 메커니즘 추가)

여러분은 DogDetailPage를 추가했지만 해당 페이지로 이동할 수 없다. 자~ 라우팅을 추가해 보자.

모든 강아지 목록을 나열하는 메인 페이지상에서 모든 card는 tap하면 강아지의 상세 페이지로 이동하는 버튼이 될 것이다.

dog_card.dart안에 DogDetailPage을 import하자.

1
2
3
4
5
6
// dog_card.dart

import 'package:flutter/material.dart';

import 'dog_detail_page.dart';
import 'dog_model.dart';

_DogCardState클래스에 모든것을 버튼(역자주: 실제 버튼이 아니라 버튼처럼 작동하는) 하나로 감싸야 한다.

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
// dog_card.dart

@override
Widget build(BuildContext context) {
// InkWell은 자식들을 태핑(tappable) 할 수 있게 해주는 특별한 Material위젯으로 , 탭되면 Material design 잉크(ink) 리플(ripple)을 추가함.
return InkWell(
// onTap은 탭 되면 트리거되는 callback이다.
onTap: showDogDetailPage,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
height: 115.0,
child: Stack(
children: <Widget>[
Positioned(
left: 50.0,
child: dogCard,
),
Positioned(top: 7.5, child: dogImage),
],
),
),
),
);
}

// 새로운 페이지를 만드는 builder메서드
showDogDetailPage() {
// Navigator.of(context) 는 현재 앱의 navigator에 접근한다.
// Navigators는 새로운 경로를 stack안에 'push'할 뿐만 아니라 pop 경로를 스택에서 없어지게 한다.
//
// This is the easiest way to build a new page on the fly
// and pass that page some state from the current page.
// 이것은 새로운 페이지를 만들고 해당 페이지를 현재 페이지의 일부 상태를 전달하는 가장 쉬운 방법이다.
Navigator.of(context).push(
MaterialPageRoute(
// builder methods always take context!
builder: (context) {
return DogDetailPage(dog);
},
),
);
}

이제 앱은 각 강아지 마다 페이지가 존재한다.
강아지 상세 페이지에 ‘뒤로가기’ 버튼이 있다는 것을 눈치 챘을 거다, 하지만 그에 대한 코드는 없다.
플러터는 자동으로 AppBar에 경로를 되돌리는 버튼을 추가한다. 필요한 경우 AppBar 위젯에서 재정의 할 수 있다.

Comment and share

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

Gradients Background

Gradient 배경을 추가하여 앱을 좀 더 예쁘게 만들 시간이다.

Gradient는 CSS와 마찬가지로 플러터에서도 쉽게 사용 할 수 있으며, 매우 핫(hot)하기 때문에 사용하기 좋다.

Gradient를 사용하려면 먼저 Container위젯이 필요하며, 그 안에 decoration 속성을 액세스 해야 한다.

main.dart파일에 _MyHomePageState클래스의 build메서드안 Container위젯의 decoration을 빌드 하는것으로 시작해 보자.

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
// main.dart

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.black87,
),
body: Container(
// Box decoration 추가
decoration: BoxDecoration(
// Box decoration은 gradient를 가진다.
gradient: LinearGradient(
// linear gradient가 시작되고 끝나는 위치
begin: Alignment.topRight,
end: Alignment.bottomLeft,
// 각 색상에 대해 하나의 종료점을 추가. 종료점은 0에서 1까지 증가 해야함.
// Add one stop for each color. Stops should increase from 0 to 1
stops: [0.1, 0.5, 0.7, 0.9],
colors: [
// 색상처리는 플러터의 Colors클래스 덕분에 겁나 쉬움.
Colors.indigo[800],
Colors.indigo[700],
Colors.indigo[600],
Colors.indigo[400],
],
),
),
child: Center(
child: DogList(initialDoggos),
),
),
);
}

gradients가 적용된 모습:
apply gradients

Comment and share

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

ListView and Builder Methods

지금 여러분은 여러분의 강아지를 위한 카드(역자주: 이전에 만들었던 DogCard)를 가지고 있고, 강아지들의 데이터를 목록으로 렌더링 하는 것이 더 유용 할 것이다.
플러터UI의 가장 중요한 개념중 하나는 빌더 메서드에서 자주 수행되는 UI목록 렌더링 이다.(rendering)
빌더 메서드는 기본적으로 Dart List 데이터의 각 데이터 조각에 대해 위젯을 생성한다.

첫번째로 dog_list.dart라는 신규 파일을 만들자.

1. DogList Class

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
// dog_list.dart

import 'package:flutter/material.dart';

import 'dog_card.dart';
import 'dog_model.dart';

class DogList extends StatelessWidget {
// 빌더 메서드는 목록(list)같은 데이터 집합에 의존한다.
final List<Dog> doggos;
DogList(this.doggos);

// 우선, 빌드 메서드를 평범하게 만든다.
// 위젯을 리턴하는 대신에 위젯을 리턴하는 메서드를 리턴한다.
// context를 전달하는걸 잊지 말자.
@override
Widget build(BuildContext context) {
return _buildList(context);
}

// 빌더 메서드는 거의 항상 ListView를 리턴한다.
// ListView는 Colum 또는 Row와 유사한 위젯이다.
// ListView는 스크롤 가능 여부를 알 수 있다.
// ListView는 builder라는 생성자를 가지고 있는데 이것은 목록으로 작동 할것 이라는 것을 알고 있다.

ListView _buildList(context) {
return ListView.builder(
// item수와 같은 item 카운트를 반드시 가져야 한다!
itemCount: doggos.length,
// 위젯을 리턴하는 콜백이다.
itemBuilder: (context, int) {
// 우리의 경우 각각의 강아지에 대한 DogCard이다.
return DogCard(doggos[int]);
},
);
}
}

그리고 _MyHomePageState의 build메서드를 수정하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// main.dart

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.black87,
),
body: Container(
// DogCard위제를 제거한다.
// 대신 여러분은 새로운 DogList 클래스를 사용해라.
//
// 위의 강아지 목록 데이터를 전달하라.
child: Center( // Changed code
child: DogList(initialDoggos), // Changed code
),
),
);
}

random한 강아지 사진을 보여주는 현 시점까지의 앱:
random dog image

Comment and share

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

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

Reusable Custom Card Widget

1. Dog Card 위젯 만들기

우리는 우리 강아지들을 display할 나이스한 위젯이 필요하다.

첫번째로 다음과 같은 card를 만들 것이다.
card widget

'dog_card.dart’파일을 신규로 생성한다.
파일안에서 빈 상태의 StatefulWidget을 생성한다. 생성자에서 강아지를 가져와야 한다.
당분간 이 card는 강아지의 이름만 표시 할것 이다.

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
// dog_card.dart

import 'package:flutter/material.dart';

import 'dog_model.dart';

class DogCard extends StatefulWidget {
final Dog dog;

DogCard(this.dog);

@override
_DogCardState createState() => _DogCardState(dog);
}

class _DogCardState extends State<DogCard> {
Dog dog;

_DogCardState(this.dog);

@override
Widget build(BuildContext context) {
return Text(widget.dog.name); // (역자주 : widget은 this와 동일 즉 this.dog.name으로 무관하다.)
}
}

DogCard를 나타나게 main.dart파일의 _MyHomePageState클래스의 build메서드를 수정해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/ main.dart

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Colors.black87,
),
body: Container(
child: DogCard(initialDoggos[1]), // New code
),
);
}

당연히 dog_cart.dart를 import해줘야 함.

1
2
3
4
5
6
// main.dart

import 'package:flutter/material.dart';

import 'dog_card.dart';
import 'dog_model.dart';

앱을 새로고침(refresh) 하면 바로 연결되어 있는것을(강아지의 이름) 확인 할 수 있을것이다. (역자주: 오른쪽 상단에 'Rex’라는 텍스트가 출력 된다)
자 이제 Card를 만들 시간이다.

2. Dog Card UI

이 카드는 두가지 주요 부분이 있다. 이미지와 그 밑에 있는 실제카드.

첫번째로 이미지를 만들어 보자.

아래 getter를 _DogCardState클래스에 추가.

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
// dog_card.dart

// URI를 나타내는 클래스 속성.
// Dog클래스에서 렌더링 된다.
String renderUrl;

Widget get dogImage {
return Container(
// 명시적으로 Container의 width, height를 설정할 수 있다.
// 그렇지 않으면 children의 공간만큼 차지한다.
width: 100.0,
height: 100.0,
// Decoration 속성은 컨테이너를 설정할 수 있게 해준다
// 이것은 BoxDecoration을 기대한다.
decoration: BoxDecoration(
// BoxDecorations have many possible properties.
// BoxDecorations은 여러가지 가능한 속성을 가지고 있다.
// BoxShape를 배경 이미지와 함께 사용하면 아바타 스타일로 잘라진 원circle)을 쉽게 만들 수 있다.
shape: BoxShape.circle,
image: DecorationImage(
// CSS의 imagesize와 동일한 속성.
fit: BoxFit.cover,
// NetworkImage위젯은 URL로 이미지를 가져 오는 위젯이다.
// ImageProviders (예) NetworkImage)는 이미지를 로드하거나 변경할 필요가 있을때 이상적이다.
// 오류를 방지하려면 Null체크를 사용하라.
image: NetworkImage(renderUrl ?? ''),
),
),
);
}

이미지를 보기 위해선 Dog클래스에 인터넷을 통해 이미지를 가져오게 해야 한다.
dog_cart.dart안의 _DogCardState클래스에 다음 코드를 추가하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// dog_card.dart


// State 클래스들은 state가 생성될때 이 메서드를 실행한다.
// initState에선 비동기 작업을 해선 안되기 때문에 다른 메서드에서 처리되도록 연기 시킬 것이다.
void initState() {
super.initState();
renderDogPic();
}

// 우린 Dog클래스 자체적으로 이미지를 가져오길 원하지만
// 이것은 플러터의 기본을 설명 할 수 있는 손쉬운 방법이다. (역자주: Dog클래스가 이미지를 가져오는게 맞겠지만 플러터의 기본을 설명하는데엔 아래 방식이 더 쉽다는것으로 이해됨.)
void renderDogPic() async {
// 서비스 호출
await dog.getImageUrl();
// setState는 플러터에게 변경된 모든것을 다시 렌더링 하도록 지시한다.
// setState는 비동기가 될 수 없으므로 덮어 쓸 수 있는 변수를 사용한다.
setState(() {
renderUrl = dog.imageUrl;
});
}

이제 우리는 렌더링 할 URL을 제대로 가져오는 아바타를 가지고 있다.
카드의 겹침(overlap) 모양을 얻으러면 내장된 위젯 Stack을 사용해야 한다.
Stack 위젯은 가장 자리를 기준으로 자식들을 배치한다.

즉 CSS positiontop, bottom, left, right 속성과 동일하다.
Stack 내에서 Position위젯으로 자식들을 감쌀 수 있지만 꼭 할필요는 없다.

  • Position으로 감싸진 위젯은 웹 개발용어를 사용하자면 'document flow’의 외부에 있다.기본적으로 Stack위젯의 상단 모서리인 [0,0]위치에 있다.
  • 감싸지지 않은 위젯은 배치되지 않는다.(positioned). 기본적으로 위젯의 열(column)으로 배치된 정상적인 'document flow’을 유지한다.

다음은 stack을 시작하는 방법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// dog_card.dart

@override
Widget build(BuildContext context) {
// Start with a container so we can add layout and style props:
return Container(
// Arbitrary number that I decided looked good:
height: 115.0,
// A stack takes children, with a list of widgets.
child: Stack(
children: <Widget>[
// position our dog image, so we can explicitly place it.
// We'll place it after we've made the card.
Positioned(
child: dogImage,
),
],
),
);
}

앱을 새로고침하면 상단의 모서리에 강아지 그림이 보인다.

_DocCardState의 레이아웃을 만들어 보자.

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
// dog_card.dart

Widget get dogCard {
// 새로운 컨테이너
// height와 width는 스타일링을 위한 임의의 숫자이다.
return Container(
width: 290.0,
height: 115.0,
child: Card(
color: Colors.black87,
// padding처리를 위해 자식을 Padding위젯으로 감싼다.
child: Padding(
// padding을 제어하는 클래스를 EdgeInsets라고 한다.
// EdgeInsets.only 생성자는 자식의 각 side에 명시적으로 padding을 설정하는데 사용.
padding: const EdgeInsets.only(
top: 8.0,
bottom: 8.0,
left: 64.0,
),
// Columm은 또다른 레이아웃 위젯 - stack과 비슷 -
// 자식들로 위젯의 목록을 취하고 위젯을 위에서 아래로 배치한다.
child: Column(
// 이들 정렬 속성은 CSS의 flexbox 속성과 정확하게 똑같이 작동한다.
// column의 주축은 세로축이고 `MainAxisAlignment.spaceAround` 는
// CSS의 세로로 배치된 flexbox의 'justify-content: space-around'와 동일하다.
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(widget.dog.name,
// 테마는 앱의 root에서 설정한 MaterialApp widget 이다.
// 우리가 우리 소유의 것을 정하지 않았기 때문에 기본값을 가지고 있다.
// 쉽게 변경 가능한 앱 차원의 일관된 스타일을 유지 하는데 탁월하다.
style: Theme.of(context).textTheme.headline),
Text(widget.dog.location,
style: Theme.of(context).textTheme.subhead),
Row(
children: <Widget>[
Icon(
Icons.star,
),
Text(': ${widget.dog.rating} / 10')
],
)
],
),
),
),
);
}

거의다 왔다!!, DogCard UI를 완성하기 위해 한 가지 더 할일이 있다.
build 메서드의 기본 위젯에 조금 더 스타일을 추가해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// dog_card.dart

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
height: 115.0,
child: Stack(
children: <Widget>[
Positioned(
left: 50.0,
child: dogCard,
),
Positioned(top: 7.5, child: dogImage),
],
),
),
);
}

Comment and share

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

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

#Data Model & HTTP

1. Get to a Clean Slate

모든 플러터앱은 main.dart로 시작한다. Counter앱 관련한 부분을 제거하면 다음과 같다:

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
// main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// MaterialApp is the base Widget for your Flutter Application
/// Gives us access to routing, context, and meta info functionality.
return MaterialApp(
title: 'We Rate Dogs',
// Make all our text default to white
// and backgrounds default to dark
theme: ThemeData(brightness: Brightness.dark),
home: MyHomePage(title: 'We Rate Dogs'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
/// Scaffold is the base for a page.
/// It gives an AppBar for the top,
/// Space for the main body, bottom navigation, and more.
return Scaffold(
/// App bar has a ton of functionality, but for now lets
/// just give it a color and a title.
appBar: AppBar(
/// Access this widgets properties with 'widget'
title: Text(widget.title),
backgroundColor: Colors.black87,
),
/// Container is a convenience widget that lets us style it's
/// children. It doesn't take up any space itself, so it
/// can be used as a placeholder in your code.
body: Container(),
);
}
}

2. Dog Model 클래스 생성.

data model을 위해 Dog이라는 일반 Dart클래스를 만든다.

첫번째로 dog_model.dart라는 파일을 lib 폴더안에 생성한다.

1
2
3
- lib
-dog_model.dart
-main.dart

저 파일에서 몇 가지 속성을 가진 수퍼 기본 클래스 (super basic class)를 만들 것이다.

1
2
3
4
5
6
7
8
9
10
11
class Dog {
final String name;
final String location;
final String description;
String imageUrl;

// 모든 강아지는 10점부터 출발한다. 왜냐면 다 좋은 강아지니깐~
int rating = 10;

Dog(this.name, this.location, this.description);
}

3. 강아지 사진 얻기

우리는 강아지 이미지를 생성하기 위해 API키 또는 다른 어떠한것도 필요없는 매우 간단한 API를 사용할 것이다.
random하게 강아지 이미지를 찾아주는 dog.ceo를 이용할 것이다.

Dog클래스에 다음 메서드를 추가하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// dog_model.dart

Future getImageUrl() async {
// Null check so our app isn't doing extra work.
// If there's already an image, we don't need to get one.
if (imageUrl != null) {
return;
}

// This is how http calls are done in flutter:
HttpClient http = HttpClient();
try {
// Use darts Uri builder
var uri = Uri.http('dog.ceo', '/api/breeds/image/random');
var request = await http.getUrl(uri);
var response = await request.close();
var responseBody = await response.transform(utf8.decoder).join();
// The dog.ceo API returns a JSON object with a property
// called 'message', which actually is the URL.
imageUrl = json.decode(responseBody)['message'];
} catch (exception) {
print(exception);
}
}

주의: 위 코드를 추가 하려면 두개의 Dart패키지를 import해야 함.

1
2
import 'dart:convert'; // json string 변환을 위함.
import 'dart:io'; // HttpClient를 위함.

4. new Dog class로 약간의 sample 데이터 생성.

main.dart에서 강아지 몇마리를 생성해 보자.

첫번째로 dog_model.dart를 import하자.

1
2
3
4
5
// main.dart

import 'package:flutter/material.dart';

import 'dog_model.dart';

그리고 약간의 강아지를 추가.

1
2
3
4
5
6
7
8
9
10
11
12
// main.dart in the State class

class _MyHomePageState extends State<MyHomePage> {
List<Dog> initialDoggos = []
..add(Dog('Ruby', 'Portland, OR, USA',
'Ruby is a very good girl. Yes: Fetch, loungin\'. No: Dogs who get on furniture.'))
..add(Dog('Rex', 'Seattle, WA, USA', 'Best in Show 1999'))
..add(Dog('Rod Stewart', 'Prague, CZ',
'Star good boy on international snooze team.'))
..add(Dog('Herbert', 'Dallas, TX, USA', 'A Very Good Boy'))
..add(Dog('Buddy', 'North Pole, Earth', 'Self proclaimed human lover.'));
}

Comment and share

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

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

Basic Dogs App Setup ( 기본 강아지 앱 설정)

이것은 굉장한 프레임워크다.

이 기본 앱 튜토리얼에서는 2012년 We Rate Dogs vs Brant 대결을 통해 역대 최고의 Twitter대화에서 영감을 받아 매우 간단하고 순수한 플러터 앱을 만들 것이다. (역자주: 2010년 트위터의 WeRateDogs라는 계정이 애완견 사진을 올리고 사용자들끼리 평가와 대화를 하는 트윗에 Brant라는 계정이 딴지를 걸기 시작한 사건으로 제가 확인은 안해봤지만 단일 대화 주제로는 트위터 설립이래 최고의 대화가 오갔다고 함.)

여기서 우리는 추가 package를 가져오지(사용하지) 않을 것이며 아키텍쳐나 state관리에 대해 생각하지 않을 것이다.

이 작업이 끝날 때쯤이면, 플러터가 우리에게 얼마나 많은 것을 주었는지에 대해 상당히 놀랄것이다. 그리고 우리는 거의 모든 것을 커버하지 않을 것이다.(역자주: 플러터가 제공하는 수 많은 기능에 대해 다루진 않겠다는 의미 인듯.)

우리가 무엇을 만들지 보자.
dogs1 dogs2

완성된 코드 링크

우리는 새로운 앱에서 시작할 것이다. 일단 플러터 설치 및 설정을 하고 난 후 (설치 및 설정 되어 있다면 pass) 새로운 앱을 만든다.

1
2
3
flutter create basic_flutter_app
cd basic_flutter_app
flutter run

또는 Github에서 이미 생성된 app을 clone받아도 된다.

1
2
3
4
git clone https://github.com/ericwindmill/flutter_by_example_apps.git
cd flutter_by_example_apps/blank_flutter_app
flutter packages get
flutter run

이것은 새롭고, 빈(?) 플러터 앱을 제공한다.

Comment and share

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

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

Stateful Widget Lifecycle(생명주기)

플러터가 StatefulWidget을 만들 때, State객체를 만든다. 이 개체는 해당 위젯의 모든 가변 상태가 유지 되는 곳이다.

state의 개념은 두 가지로 정의 된다:

  1. 위젯이 사용하는 데이터는 변경 될 수 있다.
  2. 위젯이 빌드 될때 데이터를 동시에(synchronously) 읽을 수 없다. (모든 state는 build 메서드가 호출 될때 까지 설정 되어야 한다.)

라이프 싸이클은 다음과 같은 간단한 단계를 포함하고 있다.

StatefulWidget 과 State 클래스는 별도로 분리되었는가?

한마디로 말하면 성능이다.

State객체는 오래 유지되지만 StatefulWidgets(및 모든 Widget의 서브클래스)는 구성이 변경 될때마다 폐기하고 다시 작성된다. 플러터가 변경 가능한(mutable) 위젯을 다시 작성하는것은 매우 저렴하다.

State는 재구축(rebuild)할때 마다 폐기되지 않으므로 비용 큰 계산을 피할 수 있으며 프레임별로(frame)로 프레임이 재구성 될때 마다 state속성, getter, setter등에서 가져온다.

중요한것은 이것이 플러터 애니메이션을 존재하도록 한다는 것이다. State는 폐기되지 않으므로 데이터 변경에 대한 응답으로 필요할 때 언제든지 위젯을 재 구성 할 수 있다.

1. createState()

플러터가 StatefulWidget을 빌드하도록 지시하면 즉시 [createState()]가 호출된다. 이 메서드는 반드시 존재해야 한다.(역자주: createState()를 말함) StatefulWidget은 이것보다 더 복잡할 필요가 거의 없다.

1
2
3
4
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}

2. mounted is true

createState가 state클래스를 생성하면 buildContext는 state에 할당 된다.
BuildContext는 위젯이 배치된 위젯 트리의 위치를 단순화 한 것이다.

모든 위젯은 bool형식의 this.mounted 속성을 가지고 있다. buildContext가 할당되면 true를 리턴한다. 위젯이 unmounted상태일때 setState를 호출하면 error가 발생한다.

1
tip: 이 속성은 state에 `setState()`를 호출할때 유용하지만 해당 메서드가 언제, 얼마나 자주 호출 되는지는 명확하지 않다. 아마도 stream이 업데이트에 대한 응답으로 호출되는것 같다. `if (mounted) {...` 를 사용하여 `setState()`호출전에 State가 존재하는지 확인 할 수 있디.

3. initState()

위젯이 생성될때 처음으로 호출되는 메서드 이다.(물론 class생성자 다음 입죠~)
initState는 오직 한번 만 호출 된다. 또한 반드시 super.initState()를 호출해야 한다.

initState에서 실행되면 좋은 것들

  1. 생성된 위젯 인스턴스의 BuildContext에 의존적인 것들의 데이터 초기화
  2. 동일 위젯트리내에 부모위젯에 의존하는 속성 초기화
  3. Stream 구독, 알림변경, 또는 위젯의 데이터를 변경할 수 있는 다른 객체 핸들링.
1
2
3
4
5
6
7
8
9
@override
initState() {
// 부모의 initState호출
super.initState();
// 이 클래스애 리스너 추가
cartItemStream.listen((data) {
_updateWidget(data);
});
}

4. didChangeDependencies()

didChangeDependencies 메서드는 위젯이 최초 생성될때 initState 다음에 바로 호출 된다.
또한 위젯이 의존하는 데이터의 객체가 호출될때마다 호출된다. 예를 들면 업데이트되는 위젯을 상속한 경우.
공식문서 또한 상속한 위젯이 업데이트 될때 네트워크 호출(또는 다른 비용이 큰 액션)(역자주: API호출)이 필요한 경우 유용하다고 함.

5. build()

이 메서드는 자주 호출된다(fps + render로 생각하세요.). 필수이며 재정의 대상(@override)이고 반드시 Widget을 리턴해야 한다.

플러터의 모든 gui는 Padding, Center 조차도 child 또는 children을 가진 위젯 이라는것을 기억하라.

6. didUpdateWidget(Widget oldWidget)

didUpdateWidget()는 부모 위젯이 변경되어 이 위젯을 재 구성해야 하는 경우(다은 데이터를 제공 해야하기 때문)

이것은 플러터가 오래동안 유지 되는 state를 다시 사용하기 때문이다. 이 경우 initState()에서 처럼 읿부 데이터를 다시 초기화 해야 한다.

build() 메서드가 Stream이나 변경 가능한 데이터에 의존적인경우 이전 객체에서 구독을 취소하고 didUpdateWidget()에서 새로운 인스턴스에 다시 구독 해야함.

tip: 이 메서드는 기본적으로 위젯의 상태와 관련된 위젯을 재 구성해야 하는 경우 initState()을 대치한다.

플러터는 항상 이 메서드 수행 후에 build()메서드 호출 하므로, setState() 이후 모든 추가 호출은 불필요 하다.

1
2
3
4
5
6
@override
void didUpdateWidget(Widget oldWidget) {
if (oldWidget.importantProperty != widget.importantProperty) {
_init();
}
}

7. setState()

setState() 메서드는 플러터 프레임워크 자체적, 또는 개발자로 부터 자주 호출된다.
'데이터가 변경되었음’을 프레임워크에 알리는데 사용되며 build context의 위젯을 다시 빌드하게 한다.
setState()는 비동기적이 않은 callback을 사용한다.(역자주: callback으로 비동기를 사용할 수 없다는 말임).

8. deactivate()

이 메서드는 거의 사용되지 않는다.
deactivate()는 tree에서 State가 제거 될때 호출 된다. 그러나 현재 프레임 변경이 완료되기 전에 다시 삽입 될 수 있다. 이 메서드는 State객체가 tree의 한 지점에서 다른 지점으로 이동 할 수 있기 때문에 기본적으로 존재한다.
필요에 따라 자주 호출 할 수 있는 이유는 다시 그리(repainting)는데 소용되는 비용이 저렴하기 때문이다. :-)

1
2
3
void updateProfile(String name) {
setState(() => this.name = name);
}

9. dispose()

dispost()State객체가 영구히 제거 된다.

10. mounted is false

이 상태에서 state 객체는 결코 다시 mount되지 않으며, setState()가 호출되면 에러가 발생한다.

Comment and share

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

본 문서는 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 대한민국 라이선스에 따라 이용할 수 있습니다.
Author's picture

Jace Shim


Seoul, Korea