flutter

Write Your First Flutter App, part 1

paulaner80 2019. 3. 15. 12:09
반응형

1 부에서 배울 점

Flutter 앱의 기본 구조.

패키지 사용하기

hot reload

stateful widget

무한 지연 리스트


2부에서 배울점.

테마수정

navigate (플러터에서는 route 라고 불림.)


Flutter SDK와 에디터가 있다고 가정하고 시작합니다.




3. 플러터 앱 생성

ctrl + shift + p 누르고 Flutter>new project선택





프로젝트 이름을 startup_namer으로 플러터 앱을 생성하고,


lib/main.dart에 기존에 있던 내요을 지우고 아래 코드를 입력 합니다.


[lib/main.dart]

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Welcome to Flutter",
home: Scaffold(
appBar: AppBar(title: Text("어서오시고")),
body: Center(
child: Text("헬로월드"),
),
));
}
}



애뮬레이터 lauch emulator 하고 

디버깅하지 않고 시작 혹은 디버깅을해봅니다.

헬로월드 부분을 다른 문구로 바꾸면서 hot reload되는 것을 확인합니다.



소스를 보면

1. MaterialApp을 사용함

2. main() 함수는 굵은 화살표 함수를 사용함.

3. StatelessWidget을 상속 받습니다.  Flutter에서는 거의 모든 것이 위젯입니다. 

4. Scaffold는 appbar, ttile, body 속성들을 제공

5. widget은 build 메소드를 제공.




4. 외부 패키지 사용하기

english_word 패키지를 사용해봅니다.

pub.dartlang.org. 에서 많은 패키지들을 찾을 수 있습니다.



Flutter에서는 pubspec 파일이 asset들을 관리 합니다.

pubspec.yaml 파일의 dependencies 항목 목록에 english_words: ^3.1.0을 추가합니다.


[pubspec.yaml 파일]

dev_dependencies:
flutter_test:
sdk: flutter

english_words: ^3.1.0



저장하면 자동으로 flutter packages get 이 실행됩니다.

VSCode 출력창에 아래와 같은 메시지가 나타납니다.


[VSCode 출력창]

[startup_namer] flutter packages get
Running "flutter packages get" in startup_namer... 1.7s
exit code 0


import 'package:english_words/english_words.dart';  추가


english_words 패키지를 통해서 문구를 만들 것입니다.


[현재까지 lib/main.dart]

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

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

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
final wordPair = WordPair.random();
return MaterialApp(
title: "Welcome to Flutter",
home: Scaffold(
appBar: AppBar(title: Text("어서오시고")),
body: Center(
child: Text(wordPair.asPascalCase),
),
));
}
}




5. Statefull 위젯 추가하기


Stateless 위젯은 불변이다. 모든 변수가 final 이고 프라퍼티가 변하지 않는 다는 뜻이다. 


Stateful 위젯의 state는 위젯의 생명주기 동안 변할 수 있다. 

Stateful위젯은 State 클래스의 객체를 생성하는 Statefulwidget 클래스를 요구한다.

Stateful 위젯 자체는 불변이다. 하지만 State클래스는 위젯의 생명주기 동안 유지됩니다.


이 번에는 RadomWordsState라는 State 클래스를 가지는 RandomWords라는 Stateful 위젯을 추가하고 해보자.

그런다음 RandomWords 기조의 stateless 위젯인 MyApp을의 자식으로 사용해봅자.


파일 아래에 다음과 같이 추가해보자


class RandomWordState extends State<RandomWords> {
@override
Widget build(BuildContext context) {
return null;
}
}


State<RandomWords>는 RandomWords가 제네릭 State 클래스를 확장 것을 알려줍니다.

대부분의 앱로직과 상태유지를 여기서 합니다. 여기서 RandomWords 위젯의 상태를 유지합니다.

이 클래스는 사용자가 스크롤 할 때 무한히 증가하는 생성 된 단어 쌍과 마음에 드는 아이콘을 전환하여 목록에서 단어 쌍을 추가하거나 제거 할 때 좋아하는 단어 쌍을 저장합니다 (파트 2).

RandomWordsState는 RandomWords 클래스에 따라 달라집니다. 다음에 추가 할 것입니다



class RandomWords extends StatefulWidget {
@override
RandomWordState createState() => RandomWordState();
}


state클래스를 추가한 후 IDE가 클래스에 빌드 메서드가 없음을 알려줍니다.

MyApp에 있던 단어 생성코드를 RandomWordsStatedd의 빌드 메서드로 옮겨줍니다.


class RandomWordState extends State<RandomWords> {
@override
Widget build(BuildContext context) {
final WordPair wordPair = WordPair.random();
return new Text(wordPair.asPascalCase);
}
}





6. 무한 스크롤 리스트 뷰 만들기


이제 RandomWordState를 확장하여 단어목록을 생성하고 표시해봅니다. 

사용자가 스크롤 하면 리스트뷰 위젯에 보여지는 리스트는 무한히 커집니다. 

ListView의 빌더팩토리 생성자가 필요할 때 레이지하게 빌드할 것입니다.


_suggestions 목록을 추가하여 제안된 단어들을 저장하세요. 

그리고 _biggerFont 변수로 폰트사이즈를 크게 하세요.

변수이름앞에 언더스코어를 붙이면 지역변수가 됩니다.


class RandomWordsState extends State<RandomWords> {
final List<WordPair> _suggestions = <WordPair>[];
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
(생략)



그런다음 _buildSuggestion() 함수를 RandomWordsState 클래스에 추가합니다.

이 메소드는 제안된 단어들을 보여줄 ListView를 만들어 줄것입니다.


ListView 클래스는  팩토리 빌더인 itemBuilder를 제공합니다. 이 itemBuilder는 익명함수로 지정되어 있습니다.


아래의 _buildSuggestions 함수를 RandomState 클래스에 추가합니다.

itemBuilder는 한번 단어가 재안될 때 한 번 호출 됩니다. 그리고 각 제안을 ListTile 행에 배치합니다.

짝수행의 경우 ListTile행에 추가하고 홀수 행 인겨우 Divider위젯을 추가하여 항목을 시각적으로 분리합니다.



[RandomWordsState 클래스]

_buildSuggestion() {
return new ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (BuildContext _context, int i) {
if (i.isOdd) {
return new Divider();
}

final int index = i ~/ 2; //ListView의 단어쌍의 진짜 숫자를 계산한다.
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(5));
}

return _buildRow(_suggestions[index]);
},
);
}



_buildrow 함수도 추가해줍니다.


_buildRow(WordPair pair) {
return ListTile(
title: Text(pair.asPascalCase, style: _biggerFont),
);
}



RandomWordsState가 _buildSuggestions를 사용하도록 builde 메소드를 업데이트해줍니다.

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Startup name generator")),
body: _buildSuggestion());
}


MyApp의 build 메소드도 업데이트 해줍니다.


class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(title: "Welcome to Flutter", home: RandomWords());
}
}



7. list에 아이콘 추가하기


이제 하트 아이콘을 각각의 행에 추가해보겠습니다.

탭해서 맘에드는 것을 저장하는 것은 다음 단계에서 하겠습니다.


_saved 라는 Set 객체를 RandomWordsState에 추가합니다.

이 Set은 사용자가 마음에 들어하는 단어쌍을 저장할 것입니다.

Set은 중복 입력을 허용하지 않기때문에 List 보다 더 좋은 선택입니다.



_buildRow함수에서 alreadySaved 변수를 추가합니다.

단어쌍이 즐겨찾기에 추가 되지 않았는지를 나타냅니다.



final bool alreadySaved = _saved.contain(pair)


_buildRow() 함수에서는 한트모양의 아이콘을 ListTile 객체의 추가하여 즐겨찾기를 활성화 할 수 있습니다.

하트아이콘과의 상호작용은 다음 단계에서 할 것 입니다.


8. 즐겨찾기 추가 동작

이번에는 하트아이콘을 탭 할 수 있도록 만들어 보겠습니다.

사용자가 목록의 항목을 탭하면, 즐겨찾기 상태를 토글 시킬 것입니다.

즐겨찾기 상태에 땨라 단어쌍이 Set에 저장되거나 삭제 될 것입니다.



9. 새로운 화면으로 네이게이션


이번에는 즐겨찾기를 보여줄 새로운 페이지(플러터에서는 route라고 부릅니다.)를 추가해 보겠습니다. 

어떻게 홈 라우트와 새로운 라우트를 네비게이트 하는지 알아 볼것입니다.

플러터에서는 네비게이터는 앱의 라우터를 포함하는 스택을 관리합니다.

네이게이터의 스택으로 라우터는 푸시하면 해당 경로를 화면에 업데이트합니다.

네비게이터의 스택에서 라우터를 팝하면 화면이 이전 라우터로 돌아갑니다.


그럼, RandomWordsState의 build 메소에서 AppBar에 리스트아이콘을 추가합니다.

사용자가 그 리스트아이콘을 클릭하면 저장된 즐겨찾기가 포함된 라우트가 네비게이터로 푸쉬됩니다.


>> 아이콘과 해당하는 동작을 build 메소드에 추가하기



다음으로 라우트를 만들고 네비게이터에 푸쉬합니다. 이 동작은 새로운 라우트를 보여주도록 화면을 변경합니다.

새로운 페이지의 내용은 MaterialPageRoute의 빌드 프라퍼티에 익명합수로 작성돕니다.


>>아래처럼, Navigator.push를 호출하여 라우트를 네이게이션 스택으로 푸쉬합니다.



다음 MaterialPageRout와 빌더를 추가하겠습니다. 

다음 ListTile을 생성하는 코드를 추가하겠습니다.

ListTitle의 divideTitle() 메소드는 각 ListTile사이에 가로 간격을 추가합니다.

divided 변수는 toList() 함수를 통해 리스트로 변환된 final rows을 가지고 있습니다.



앱바..https://flutter.dev/docs/catalog/samples/basic-app-bar


[전체소스]

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

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

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome To Flutter',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: RandomWords());
}
}

class RandomWords extends StatefulWidget {
@override
RandomWordState createState() => new RandomWordState();
}

class RandomWordState extends State<RandomWords> {
final List<WordPair> _suggestions = <WordPair>[];
final Set<WordPair> _saved = Set<WordPair>();
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Startup namer"),
actions: <Widget>[
new IconButton(
icon: Icon(Icons.list),
onPressed: _pushSave,
)
],
),
body: _buildSuggestions());
}

Widget _buildSuggestions() {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (BuildContext _context, int i) {
if (i.isOdd) {
return new Divider();
}

final int index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
},
);
}

Widget _buildRow(WordPair pair) {
final bool alreadySaved = _saved.contains(pair);
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: new Icon(alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null),
onTap: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
},
);
}

_pushSave() {
Navigator.of(context)
.push(MaterialPageRoute(builder: (BuildContext context) {
final Iterable<ListTile> titles = _saved.map((WordPair pair) {
return ListTile(
title: Text(pair.asPascalCase, style: _biggerFont),
);
});
final List<Widget> divided =
ListTile.divideTiles(context: context, tiles: titles).toList();

return Scaffold(
appBar: AppBar(title: Text("saved suggestions")),
body: new ListView(
children: divided,
));
}));
}
}


'flutter' 카테고리의 다른 글

Flutter Stream  (0) 2019.03.20
A Design Pattern for Flutter  (0) 2019.03.20
flutter-todos-tutorial-with-flutter-bloc  (0) 2019.03.20
Asynchronous Programming : Streams  (0) 2019.03.14
vs code 플러터 세팅  (0) 2019.03.12