첫 Flutter 앱 작성하기 - 1부

The app that you'll be building

첫 Flutter 앱을 만드는 방법을 안내합니다. 변수, 반복문, 조건문 등 기본 프로그래밍 개념과 객체지향에 친숙하다면, 이 튜토리얼을 완료할 수 있습니다. Dart 경험이나 모바일, 웹 프로그래밍 경험이 없어도 상관없습니다.

이 가이드는 코드랩 2부 중 1부입니다. 2부Google Developers에서 보실 수 있습니다. 1부 또한 Google Developers에서도 볼 수 있습니다.

1부에서 무엇을 만드는가

스타트업 회사를 위해 이름을 생성하여 제안하는 간단한 모바일 앱을 구현할 것입니다. 사용자는 이름을 선택하거나 선택을 취소할 수 있으며, 가장 좋은 이름을 저장할 수 있습니다. 코드는 lazy하게 이름을 생성합니다. 사용자가 스크롤하면 더 많은 이름이 생성됩니다. 무한하게 스크롤 할 수 있습니다.

GIF 애니메이션은 1부를 완료하면 앱이 어떻게 동작하는지를 보여줍니다.

1단계: Starter Flutter app 만들기

첫 Flutter 앱 시작하기에 있는 지침을 따라 간단한 템플릿 기반 Flutter 앱을 만듭니다. 이름을 startup_namer로 지정합니다 (myapp 대신).

코드랩에서는 주로 Dart 코드가 있는 lib/main.dart를 수정할 것입니다.

  1. lib/main.dart의 내용을 바꿉니다.
    lib/main.dart의 모든 코드를 삭제합니다. 화면 중앙에 “Hello World”를 표시하는 아래 코드로 대체합니다.

    lib/main.dart
    // Copyright 2018 The Flutter team. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Welcome to Flutter',
          home: Scaffold(
            appBar: AppBar(
              title: Text('Welcome to Flutter'),
            ),
            body: Center(
              child: Text('Hello World'),
            ),
          ),
        );
      }
    }
  2. IDE에 적합한 방식으로 앱을 실행하세요. 장치에 따라, Android 또는 iOS 결과가 나와야 합니다.

    Hello world app on Android
    안드로이드
    Hello world app on iOS
    iOS

관찰

  • 이 예제는 머티리얼 앱을 만듭니다. 머티리얼은 모바일 및 웹에서 표준으로 사용되는 시각 디자인 언어입니다. Flutter는 다양한 머티리얼 위젯을 제공합니다.
  • main() 메서드는 화살표(=>) 표기법을 사용합니다. 한 줄 함수 또는 메서드에 화살표 표기법을 사용하세요.
  • 앱은 StatelessWidget을 상속받아 앱 자체를 위젯으로 만듭니다. Flutter에서는 정렬, 여백, 레이아웃 등 거의 모든것이 위젯입니다.
  • 머티리얼 라이브러리의 Scaffold 위젯은 홈 스크린의 위젯 트리를 구성하는 app bar, title, body 속성을 기본으로 제공합니다.
    위젯 하위 트리는 상당히 복잡할 수 있습니다.
  • 위젯의 주된 임무는 다른 하위 위젯을 어떻게 표현할 지를 설명하는 build() 메서드를 제공하는 것입니다.
  • 이 예제는 자식 위젯으로 Text을 포함하는 Center 위젯으로 구성됩니다. Center 위젯은 하위 위젯을 화면 중앙에 정렬합니다.

2단계: 외부 패키지 이용하기

이 단계에서는 가장 많이 사용되는 영어 단어 수천 개와 몇 가지 유틸리티 기능이 포함되어 있는 오픈 소스 패키지인 english_words를 이용할 것입니다.

다른 오픈 소스 패키지와 마찬가지로, [the Package site][]에서 english_words 패키지를 찾을 수 있습니다.

  1. Flutter 앱에서 의존성 및 asset 관리는 pubspec 파일이 담당합니다. pubspec.yaml의 의존성 목록에 english_words (3.1.0 이상)를 추가하세요:

    {step1_base → step2_use_package}/pubspec.yaml
    @@ -5,4 +5,5 @@
    5
    5
    dependencies:
    6
    6
    flutter:
    7
    7
    sdk: flutter
    8
    8
    cupertino_icons: ^0.1.2
    9
    + english_words: ^3.1.0
  2. 안드로이드 스튜디오의 편집기 화면에서 pubspec 파일을 보는 동안 나타나는 Packages get를 클릭하세요. 이렇게 하면 해당 패키지를 프로젝트로 가져옵니다. 아래와 같은 메시지를 콘솔에서 확인할 수 있습니다:

    $ flutter pub get
    Running "flutter pub get" in startup_namer...
    Process finished with exit code 0
    

    또한 Packages get을 수행하면 pubspec.lock 프로젝트로 가져온 모든 패키지 목록과 버전 번호를 포함하고 있는 pubspec.lock 파일도 자동으로 생성됩니다.

  3. lib/main.dart에서 새 패키지를 가져오세요:

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

    입력 할 때, 안드로이드 스튜디오가 import 할만한 라이브러리를 추천해줍니다. 그런 다음 import 문자열이 회색으로 나타내어 해당 라이브러리가 (지금까지는) 사용되지 않았음을 알려줍니다.

  4. “Hello World” 문자열 대신 English words를 사용하여 텍스트를 생성하세요:

    {step1_base → step2_use_package}/lib/main.dart
    @@ -9,6 +10,7 @@
    9
    10
    class MyApp extends StatelessWidget {
    10
    11
    @override
    11
    12
    Widget build(BuildContext context) {
    13
    + final wordPair = WordPair.random();
    12
    14
    return MaterialApp(
    13
    15
    title: 'Welcome to Flutter',
    14
    16
    home: Scaffold(
    @@ -16,7 +18,7 @@
    16
    18
    title: Text('Welcome to Flutter'),
    17
    19
    ),
    18
    20
    body: Center(
    19
    - child: Text('Hello World'),
    21
    + child: Text(wordPair.asPascalCase),
    20
    22
    ),
    21
    23
    ),
    22
    24
    );
  5. 앱이 실행중이면, hot reload를 사용하여 실행중인 앱을 업데이트하세요. 실행중인 앱에서 hot reload를 클릭할 때마다 혹은 프로젝트를 저장할 때마다, 랜덤하게 선택된 다른 단어 쌍을 볼 수 있을 것입니다. 왜냐하면 MaterialApp이 렌더링 될 때마다 혹은 또는 Flutter Inspector에서 플랫폼을 전환할 때마다 실행되는 build 메서드 안에서 단어 쌍을 생성하고 있기 때문입니다.

    App at completion of second step on Android
    안드로이드
    App at completion of second step on iOS
    iOS

문제가 있나요?

앱이 올바르게 동작하지 않는다면, 오타를 확인해보세요. Flutter 디버깅 툴을 사용해보고 싶다면, DevTools 제품군과 프로파일링 툴을 확인해보세요. 필요하다면, 아래 코드를 사용하여 다시 올바르게 동작하게 하세요.

3단계: Stateful 위젯 추가하기

Stateless 위젯은 변경불가능immutable합니다. 속성을 변경할 수 없습니다—모든 값이 final입니다.

Stateful 위젯은 위젯의 수명동안 변경될 수 있는 상태를 유지합니다. Stateful 위젯은 최소 두 개 이상 클래스가 필요합니다: 1) StatefulWidget 클래스가 2) State 클래스 의 인스턴스를 생성합니다. StatefulWidget 클래스 그자체는 변경불가능합니다. 하지만 State 클래스가 위젯의 수명동안 상태를 유지합니다.

이 단계에서는, Stateful 위젯 RandomWords를 추가하고, 그 위젯에서 State 클래스인 RandomWordsState를 생성할 것입니다. 그런 다음 RandomWords를 기존 Stateless 위젯 MyApp의 자식으로 사용할 것입니다.

  1. 최소한의 상태를 가지는 클래스를 생성하세요. main.dart의 하단에 아래 코드를 추가하세요:

    lib/main.dart (RandomWordsState)
    class RandomWordsState extends State<RandomWords> {
      // TODO Add build() method
    }

    State<RandomWords> 선언을 눈여겨보세요. RandomWords로 지정된 제네릭으로 State 클래스를 사용하고 있습니다. 대부분의 앱 로직과 상태는 여기서 유지됩니다—RandomWords 위젯을 위해 상태를 보관합니다. 이 클래스에 생성된 단어 쌍이 저장됩니다. 사용자가 스크롤함에 따라 단어 쌍이 무한히 증가하고, 사용자가 하트 아이콘 스위치를 눌러 가장 좋아하는 단어 쌍을 지정할 수 있습니다(part 2에서).

    RandomWordsStateRandomWords 클래스에 의존적입니다. 아래에서 추가할 것입니다.

  2. Stateful 위젯 RandomWordsmain.dart에 추가하세요. RandomWords 위젯은 상태 클래스를 만드는 것 이외에 별다른 일을 하지 않습니다:

    lib/main.dart (RandomWords)
    class RandomWords extends StatefulWidget {
      @override
      RandomWordsState createState() => RandomWordsState();
    }

    상태 클래스를 추가한 후, IDE가 클래스에 build 메서드가 없다고 경고합니다. 다음으로, 기본 메서드 build를 추가하고 단어 생성 코드를 MyApp에서 RandomWordsState로 옮겨 단어 쌍을 생성하도록 하세요.

  3. build() 메서드를 RandomWordsState에 추가하세요:

    lib/main.dart (RandomWordsState)
    class RandomWordsState extends State<RandomWords> {
      @override
      Widget build(BuildContext context) {
        final wordPair = WordPair.random();
        return Text(wordPair.asPascalCase);
      }
    }
  4. 아래 diff에 표시된 변경사항처럼 MyApp에서 단어 생성 코드를 삭제하세요:

    {step2_use_package → step3_stateful_widget}/lib/main.dart
    @@ -10,7 +10,6 @@
    10
    10
    class MyApp extends StatelessWidget {
    11
    11
    @override
    12
    12
    Widget build(BuildContext context) {
    13
    - final wordPair = WordPair.random();
    14
    13
    return MaterialApp(
    15
    14
    title: 'Welcome to Flutter',
    16
    15
    home: Scaffold(
    @@ -18,8 +17,8 @@
    18
    17
    title: Text('Welcome to Flutter'),
    19
    18
    ),
    20
    19
    body: Center(
    21
    - child: Text(wordPair.asPascalCase),
    20
    + child: RandomWords(),
    22
    21
    ),
    23
    22
    ),
    24
    23
    );
    25
    24
    }
  5. 앱을 재시작하세요. 앱은 이전에 동작하던 방식과 마찬가지로 hot reload 하거나 저장할 때마다 단어 쌍을 계속 보여줘야 합니다.

문제가 있나요?

앱이 올바르게 동작하지 않는다면, 오타를 확인해보세요. Flutter 디버깅 툴을 사용해보고 싶다면, DevTools 제품군과 프로파일링 툴을 확인해보세요. 필요하다면, 아래 코드를 사용하여 다시 올바르게 동작하게 하세요.

4단계: 무한 스크롤 ListView 생성하기

이 단계에서는, RandomWordsState를 확장하여 단어 쌍 목록을 생성하고 표시합니다. ListView 위젯 안에 표시되는 목록이 사용자가 스크롤할 때마다 무한하게 늘어납니다. ListViewbuilder 팩토리 생성자를 사용하면 필요에 따라 lazy한 방식으로 목록을 만듭니다.

  1. 제안된 단어 쌍을 저장하기 위해 RandomWordsState 클래스에 _suggestions 목록을 추가하세요. 또한, 글자 크기를 키우기 위해 _biggerFont 변수를 추가하세요.

    lib/main.dart
    class RandomWordsState extends State<RandomWords> {
      final _suggestions = <WordPair>[];
      final _biggerFont = const TextStyle(fontSize: 18.0);
      // ···
    }

    다음으로, RandomWordsState 클래스에 _buildSuggestions() 함수를 추가하세요. 이 메서드는 제안된 단어 쌍을 표시하는 ListView를 만듭니다.

    ListView 클래스는 builder 속성인 itemBuilder를 제공합니다. 이 팩토리 빌더는 익명 함수 형태의 콜백 함수를 받습니다. 두 인자가 함수에 전달됩니다; BuildContext와 행 반복자 i입니다. 반복자는 0부터 시작되고 함수가 호출될 때마다 증가합니다. ListTile에 제안된 모든 단어 쌍에 대해 2번씩, 그리고 Divider에 1번씩 증가합니다. 이 방식을 사용하여 사용자가 스크롤을 할 때마다 목록이 무한하게 증가할 수 있게 할 수 있습니다.

  2. RandomWordsState 클래스에 _buildSuggestions() 함수를 추가하세요:

    lib/main.dart (_buildSuggestions)
    Widget _buildSuggestions() {
      return ListView.builder(
          padding: const EdgeInsets.all(16.0),
          itemBuilder: /*1*/ (context, i) {
            if (i.isOdd) return Divider(); /*2*/
    
            final index = i ~/ 2; /*3*/
            if (index >= _suggestions.length) {
              _suggestions.addAll(generateWordPairs().take(10)); /*4*/
            }
            return _buildRow(_suggestions[index]);
          });
    }
    1. itemBuilder 콜백은 단어 쌍이 제안될 때마다 호출되고 각각을 ListTile 행에 배치합니다. 짝수 행인 경우 ListTile 행에 단어 쌍을 추가합니다. 홀수 행인 경우 시각적으로 각 항목을 구분하는 Divider 위젯을 추가합니다. 작은 기기에서는 구분선을 보기 어려울 수 있습니다.
    2. ListView의 각 행 앞에 1 픽셀 높이의 구분선 위젯을 추가하십시오.
    3. i ~/ 2 표현식은 i를 2로 나눈 뒤 정수 결과를 반환합니다. 예를 들어: 1, 2, 3, 4, 5는 0, 1, 1, 2, 2가 됩니다. 이렇게 하면 구분선 위젯을 제외한 ListView에 있는 단어 쌍 수가 계산됩니다.
    4. 가능한 단어 쌍을 모두 사용하고 나면, 10개를 더 생성하고 제안 목록에 추가합니다.

    _buildSuggestions() 함수는 단어 쌍 마다 한 번 씩 _buildRow()를 호출합니다. 이 함수는 ListTile에서 각각 새로운 쌍을 표시하여 다음 단계에서 행을 더 매력적으로 만들 수 있게 합니다.

  3. RandomWordsState_buildRow()를 추가하세요:

    lib/main.dart (_buildRow)
    Widget _buildRow(WordPair pair) {
      return ListTile(
        title: Text(
          pair.asPascalCase,
          style: _biggerFont,
        ),
      );
    }
  4. RandomWordsState 클래스에서 build() 메서드를 변경하여 단어 생성 라이브러리를 직접 호출하지 말고 _buildSuggestions()을 사용하도록 하세요, (Scaffold는 기본적인 머티리얼 디자인 시각 레이아웃을 구현합니다.) 메서드의 본문을 아래 강조 표시된 코드로 교체하세요:

    lib/main.dart (build)
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Startup Name Generator'),
        ),
        body: _buildSuggestions(),
      );
    }
  5. MyApp 클래스에서 build() 메서드를 수정하세요. title을 변경하고 home을 RandomWords으로 변경하세요:

    {step3_stateful_widget → step4_infinite_list}/lib/main.dart
    @@ -10,15 +10,8 @@
    10
    10
    class MyApp extends StatelessWidget {
    11
    11
    @override
    12
    12
    Widget build(BuildContext context) {
    13
    13
    return MaterialApp(
    14
    - title: 'Welcome to Flutter',
    15
    - home: Scaffold(
    14
    + title: 'Startup Name Generator',
    15
    + home: RandomWords(),
    16
    - appBar: AppBar(
    17
    - title: Text('Welcome to Flutter'),
    18
    - ),
    19
    - body: Center(
    20
    - child: RandomWords(),
    21
    - ),
    22
    - ),
    23
    16
    );
    24
    17
    }
  6. Restart the app. You should see a list of word pairings no matter how far you scroll.

    App at completion of fourth step on Android
    안드로이드
    App at completion of fourth step on iOS
    iOS

문제가 있나요?

앱이 올바르게 동작하지 않는다면, 오타를 확인해보세요. Flutter 디버깅 툴을 사용해보고 싶다면, DevTools 제품군과 프로파일링 툴을 확인해보세요. 필요하다면, 아래 코드를 사용하여 다시 올바르게 동작하게 하세요.

Profile or release runs

So far you’ve been running your app in debug mode. Debug mode trades performance for useful developer features such as hot reload and step debugging. It’s not unexpected to see slow performance and janky animations in debug mode. Once you are ready to analyze performance or release your app, you’ll want to use Flutter’s “profile” or “release” build modes. For more details, see Flutter’s build modes.

다음 단계

2부에서 만들 앱
2부에서 만들 앱

축하합니다!

iOS와 Android 모두에서 작동하는 인터랙티브한 Flutter 앱을 작성해보았습니다. 이 코드랩에서:

  • Flutter 앱을 처음부터 만들었습니다.
  • Dart 코드를 작성했습니다.
  • 외부 서드파티 라이브러리를 활용했습니다.
  • 빠른 개발 사이클을 위해 hot reload를 사용했습니다.
  • Stateful 위젯을 적용했습니다.
  • Lazy한 방식 무한 스크롤 목록을 만들었습니다.

이 앱을 확장하고 싶다면, 구글 개발자 코드랩 사이트에서, 2부를 진행하세요. 아래 기능을 추가하게 될 것입니다:

  • 클릭 가능한 하트 아이콘을 추가하여 가장 좋아하는 단어 쌍을 저장하고 앱을 인터랙티브하게 만들어봅니다.
  • 가장 좋아하는 단어를 보관하는 새로운 화면과 경로를 추가하고 그 화면으로 이동하는 내비게이션 기능을 구현해봅니다.
  • 테마 색을 수정하여 흰색 앱을 만들어봅니다.