첫 Flutter 앱 작성, 1부

Flutter는 하나의 코드베이스를 사용해 모바일, 웹, 데스크톱을 대상으로 아름다운 네이티브 컴파일 애플리케이션을 개발하기 위한 Google의 UI 도구 모음입니다. Flutter는 기존 코드와 호환되며 전 세계의 개발자와 조직에서 사용하고 있고, 무료 및 오픈소스로 제공됩니다.

이 Codelab에서는 간단한 모바일 Flutter 앱을 만들어봅니다. 객체 지향 코드와 변수, 루프, 조건문 같은 기본 프로그래밍 개념을 잘 알고 있다면 Codelab을 완료할 수 있습니다. 이전에 Dart, 모바일, 웹 프로그래밍을 해본 적이 없어도 괜찮습니다.

1부에서 학습할 내용

  • iOS, Android, 웹에서 자연스럽게 보이는 Flutter 앱을 작성하는 방법
  • Flutter 앱의 기본 구조
  • 기능 확장을 위한 패키지 찾기 및 사용
  • 핫 리로드를 사용한 개발 주기 단축
  • 스테이트풀(Stateful) 위젯을 구현하는 방법
  • 지연 로드되는 무제한 목록을 만드는 방법

이 Codelab의 2부에서는 상호작용을 추가하고, 앱의 테마를 수정하고, 새 페이지(Flutter에서는 경로라고 함)로 이동하는 기능을 추가합니다.

1부에서 빌드할 항목

스타트업 회사를 위한 추천 이름을 생성하는 간단한 앱을 구현합니다. 사용자는 이름을 선택, 선택 해제하고 최적의 이름을 저장할 수 있습니다. 코드는 이름을 한 번에 10개씩 지연 생성합니다. 사용자가 스크롤하면 이름이 더 생성됩니다. 사용자가 스크롤할 수 있는 범위에는 제한이 없습니다.

다음 애니메이션 GIF는 구현을 완료하면 앱이 어떻게 작동하는지 보여줍니다.

6556f8b61acd6a89.gif

이 Codelab에서 학습하고 싶은 내용은 무엇인가요?

이 주제를 처음 접하므로 개요를 파악하고 싶습니다. 이 주제에 관해 약간 알고 있지만 한 번 더 확인하고 싶습니다. 프로젝트에 사용할 코드 예를 찾고 있습니다. 특정 항목에 관한 설명을 찾고 있습니다.

이 실습을 완료하려면 Flutter SDK편집기라는 두 가지 소프트웨어가 필요합니다. 이 Codelab에서는 Android 스튜디오를 사용한다고 가정하지만, 원하는 편집기를 사용해도 됩니다.

다음 기기 중 하나를 사용하여 이 Codelab을 실행할 수 있습니다.

  • 컴퓨터에 연결되어 있으며 개발자 모드로 설정된 실제 Android 또는 iOS 기기
  • iOS 시뮬레이터(Xcode 도구 설치 필요)
  • Android Emulator(Android 스튜디오 설정 필요)
  • 브라우저(디버깅 시 Chrome 필요)

b2f84ff91b0e1396.png간단한 템플릿 Flutter 앱을 만듭니다. 다음과 같이 startup_namer라는 Flutter 프로젝트를 만들고 null 안전성으로 이전합니다.

$ flutter create startup_namer
$ cd startup_namer
$ dart migrate --apply-changes

대체로 Dart 코드가 있는 lib/main.dart를 수정합니다.

b2f84ff91b0e1396.png lib/main.dart의 내용을 바꿉니다. lib/main.dart에서 모든 코드를 삭제하고 화면 가운데에 'Hello World'를 표시하는 다음 코드로 바꿉니다.

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: const Text('Welcome to Flutter'),
        ),
        body: const Center(
          child: const Text('Hello World'),
        ),
      ),
    );
  }
}

b2f84ff91b0e1396.png 앱을 실행합니다. 기기에 따라 Android 출력, iOS 출력, 웹 출력 중 하나가 표시됩니다.

Android

iOS

cf1e10b838bf60ee.png 유용한 정보

  • 이 예에서는 머티리얼 앱을 만듭니다. 머티리얼은 모바일과 웹의 표준 시각 디자인 언어입니다. Flutter는 다양한 머티리얼 위젯을 제공합니다.
  • 기본 메서드에 화살표(=>) 표기법을 사용합니다. 한 줄 함수 또는 메서드에 화살표 표기법을 사용합니다.
  • 앱은 StatelessWidget을 확장하여 앱 자체를 위젯으로 만듭니다. Flutter에서는 정렬, 패딩, 레이아웃을 포함한 거의 모든 항목이 위젯입니다.
  • 머티리얼 라이브러리의 Scaffold 위젯은 기본 앱 바와 제목뿐만 아니라 홈 화면의 위젯 트리를 포함하는 본문 속성도 제공합니다. 위젯 하위 트리는 상당히 복잡할 수 있습니다.
  • 위젯의 기본 작업은 다른 하위 수준 위젯의 측면에서 위젯을 표시하는 방법을 설명하는 build 메서드를 제공하는 것입니다.
  • 이 예의 본문은 Text 하위 위젯을 포함하는 Center 위젯으로 구성되어 있습니다. Center 위젯은 위젯 하위 트리를 화면 중앙에 맞춥니다.

이 단계에서는 english_words라는 오픈소스 패키지를 사용하기 시작합니다. 이 패키지에는 가장 많이 사용되는 영어 단어 수천 개와 몇 가지 유틸리티 함수가 있습니다.

pub.dev에서 english_words 패키지뿐만 아니라 다수의 다른 오픈소스 패키지도 찾을 수 있습니다.

b2f84ff91b0e1396.png pubspec 파일은 Flutter 앱의 애셋을 관리합니다. pubspec.yaml에서 종속 항목 목록에 english_words: ^4.0.0(english_words 4.0.0 이상)을 추가합니다.

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  english_words: ^4.0.0   # add this line

b2f84ff91b0e1396.png Android 스튜디오의 편집기 뷰에서 pubspec 파일을 연 상태로 Packages get을 클릭합니다. 그러면 패키지가 프로젝트로 불러와 집니다. 콘솔에 다음이 표시됩니다.

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

또한 dart pub get을 실행하면 프로젝트에 가져온 모든 패키지 목록과 버전 번호가 포함된 pubspec.lock 파일이 자동으로 생성됩니다.

b2f84ff91b0e1396.png lib/main.dart에서 새 패키지를 가져옵니다.

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';  // Add this line.

입력하기 시작하면 Android 스튜디오에서 가져올 라이브러리가 추천됩니다. 그런 다음 가져오기 문자열이 회색으로 렌더링되므로 가져온 라이브러리가 아직 사용되지 않았음을 알 수 있습니다.

다음으로, 'Hello World'를 사용하는 대신 english_words 패키지를 사용하여 텍스트를 생성합니다.

b2f84ff91b0e1396.png다음과 같이 변경합니다.

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final wordPair = WordPair.random(); // Add this line.
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(                       // Drop the const, and
          //child: Text('Hello World'),     // Replace this text...
          child: Text(wordPair.asPascalCase),  // With this text.
        ),
      ),
    );
  }
}

b2f84ff91b0e1396.png 앱이 실행 중인 경우 핫 리로드 e11f6ccd1560a28b.png를 사용하여 실행 중인 앱을 업데이트합니다(명령줄에서는 r을 입력하여 핫 리로드함). 핫 리로드를 클릭하거나 프로젝트를 저장할 때마다 실행 중인 앱에서 다른 단어 쌍이 무작위로 선택되어 표시됩니다. 이것은 MaterialApp에서 렌더링해야 할 때마다 또는 Flutter Inspector에서 플랫폼을 전환하면 실행되는 build 메서드 내에서 단어 쌍이 생성되기 때문입니다.

Android

iOS

문제가 있나요?

앱이 올바르게 실행되지 않는다면 오타가 있는지 확인합니다. 필요한 경우 다음 링크의 코드를 사용하면 제대로 작동합니다.

스테이트리스(Stateless) 위젯은 변경 불가능 합니다. 즉, 속성을 변경할 수 없으며 모든 값이 최종입니다.

스테이트풀(Stateful) 위젯은 위젯의 전체 기간 동안 변경될 수도 있는 상태를 유지합니다. 스테이트풀(Stateful) 위젯을 구현하려면 클래스가 두 개 이상 필요합니다. 첫 번째는 State 클래스의 인스턴스를 만드는 StatefulWidget입니다. StatefulWidget 객체 자체는 변경 불가능하며 삭제 후 다시 생성할 수 있지만, State 객체는 위젯의 전체 기간보다 오래 지속됩니다.

이 단계에서는 State 클래스인 _RandomWordsState를 만드는 스테이트풀(Stateful) 위젯 RandomWords를 추가합니다. 그런 다음 기존의 MyApp 스테이트리스(Stateless) 위젯 내의 하위 요소로 RandomWords를 사용합니다.

b2f84ff91b0e1396.png 스테이트풀(Stateful) 위젯의 상용구 코드를 만듭니다.

이 코드는 파일에서 MyApp 외의 어느 위치에든 배치할 수 있지만 이 솔루션에서는 파일 하단에 배치합니다. lib/main.dart에서 모든 코드 뒤에 커서를 놓고 Return 키를 몇 번 눌러 새로운 줄에서 시작합니다. IDE에서 stful을 입력하기 시작합니다. 편집기에 Stateful 위젯을 만들지 묻는 메시지가 표시됩니다. Return 키를 눌러 수락합니다. 그러면 두 클래스의 상용구 코드가 표시되고 스테이트리스(Stateless) 위젯의 이름을 입력할 수 있도록 커서가 배치됩니다.

b2f84ff91b0e1396.png 위젯 이름으로 RandomWords를 입력합니다.

아래 코드에서 볼 수 있듯이 RandomWords 위젯은 State 클래스를 만드는 것 외에는 다른 작업을 거의 하지 않습니다.

스테이트풀(Stateful) 위젯 이름으로 RandomWords를 입력한 후에는 IDE에서 동반 State 클래스가 자동으로 업데이트되어 이름이 _RandomWordsState로 지정됩니다. 기본적으로 State 클래스의 이름에는 앞에 밑줄이 붙습니다. 식별자 앞에 밑줄을 사용하면 Dart 언어에서 개인 정보 보호가 적용되며 State 객체에 관해서는 이 표기를 사용하는 것이 좋습니다.

또한 IDE는 상태 클래스를 자동으로 업데이트하여 State<RandomWords>를 확장합니다. 이는 RandomWords 용도로 특화된 일반적인 State 클래스를 사용하고 있음을 나타냅니다. 이 클래스에는 앱의 로직 대부분이 보관되며⁠ RandomWords 위젯의 상태가 유지됩니다. 이 클래스는 생성된 단어 쌍의 목록을 저장합니다. 단어 쌍은 사용자가 스크롤함에 따라 무제한으로 증가하며 이 실습 2부에서는 사용자가 하트 아이콘을 전환하여 목록에서 추가하고 삭제할 때 단어 쌍을 즐겨찾기에 추가합니다.

두 클래스 모두 다음과 같이 나타납니다.

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

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

b2f84ff91b0e1396.png _RandomWordsStatebuild() 메서드를 업데이트합니다.

return Container();를 다음 두 줄로 바꿉니다.

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

b2f84ff91b0e1396.png 다음과 같이 변경하여 MyApp에서 단어 생성 코드를 삭제합니다.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final wordPair = WordPair.random();  // DELETE

    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          //child: Text(wordPair.asPascalCase), // REPLACE with...
          child: RandomWords(),                 // ...this line
        ),
      ),
    );
  }
}

b2f84ff91b0e1396.png 앱을 핫 리로드합니다. 앱은 이전과 똑같이 작동하며, 핫 리로드하거나 앱을 저장할 때마다 단어 쌍을 표시해야 합니다.

문제가 있나요?

앱이 올바르게 실행되지 않는 경우 다음 링크의 코드를 사용하면 제대로 작동할 수 있습니다.

이 단계에서는 _RandomWordsState를 확장하여 단어 쌍 목록을 생성하고 표시합니다. 사용자가 스크롤하면 목록(ListView 위젯에 표시됨)이 무제한으로 증가합니다. ListViewbuilder 팩토리 생성자를 사용하면 필요할 때 목록 보기를 지연 빌드할 수 있습니다.

b2f84ff91b0e1396.png _RandomWordsState 클래스에 일부 상태 변수를 추가합니다.

추천 단어 쌍을 저장하는 _suggestions 목록을 추가합니다. 또한 글꼴 크기를 키우기 위한 _biggerFont 변수를 추가합니다.

class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];                 // NEW
  final _biggerFont = const TextStyle(fontSize: 18); // NEW
  ...
}

다음으로, _RandomWordsState 클래스에 _buildSuggestions() 함수를 추가합니다. 이 메서드는 추천 단어 쌍을 표시하는 ListView를 빌드합니다.

ListView 클래스는 익명 함수로 지정된 팩토리 빌더 및 콜백 함수인 빌더 속성 itemBuilder를 제공합니다. BuildContext와 행 반복자 i 등 두 매개변수가 함수에 전달됩니다. 이 반복자는 0부터 시작되며 함수가 호출될 때마다 추천 단어 쌍마다 한 번씩 증분됩니다. 이 모델을 사용하면 사용자가 스크롤함에 따라 추천 단어 목록이 계속 증가할 수 있습니다.

b2f84ff91b0e1396.png 전체 _buildSuggestions 함수를 추가합니다.

원하는 경우 _RandomWordsState 클래스에서 다음 함수를 추가하여 주석을 삭제합니다.

  Widget _buildSuggestions() {
    return ListView.builder(
      padding: const EdgeInsets.all(16),
      // The itemBuilder callback is called once per suggested
      // word pairing, and places each suggestion into a ListTile
      // row. For even rows, the function adds a ListTile row for
      // the word pairing. For odd rows, the function adds a
      // Divider widget to visually separate the entries. Note that
      // the divider may be difficult to see on smaller devices.
      itemBuilder: (BuildContext _context, int i) {
        // Add a one-pixel-high divider widget before each row
        // in the ListView.
        if (i.isOdd) {
          return Divider();
        }

        // The syntax "i ~/ 2" divides i by 2 and returns an
        // integer result.
        // For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2.
        // This calculates the actual number of word pairings
        // in the ListView,minus the divider widgets.
        final int index = i ~/ 2;
        // If you've reached the end of the available word
        // pairings...
        if (index >= _suggestions.length) {
          // ...then generate 10 more and add them to the
          // suggestions list.
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      }
    );
  }

_buildSuggestions 함수는 단어 쌍별로 한 번씩 _buildRow를 호출합니다. 이 함수는 각각의 새 쌍을 ListTile로 표시하여 2부에서 더 유용한 행을 만드는 데 도움이 됩니다.

b2f84ff91b0e1396.png _buildRow 함수를 _RandomWordsState에 추가합니다.

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

b2f84ff91b0e1396.png _RandomWordsState.build 메서드를 업데이트합니다.

단어 생성 라이브러리를 직접 호출하지 않고 _buildSuggestions()를 사용하도록 변경합니다. (Scaffold는 기본 머티리얼 디자인 시각적 레이아웃을 구현합니다.)

  @override
  Widget build(BuildContext context) {
    //final wordPair = WordPair.random(); // Delete these...
    //return Text(wordPair.asPascalCase); // ... two lines.

    return Scaffold (                     // Add from here...
      appBar: AppBar(
        title: Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );                                      // ... to here.
  }

b2f84ff91b0e1396.png MyAppbuild 메서드를 업데이트하여 제목을 변경하고, AppBar를 삭제하고, 홈 속성을 RandomWords 위젯으로 변경합니다.

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      home: RandomWords(),
    );
  }

b2f84ff91b0e1396.png 앱을 다시 시작합니다. 아무리 많이 스크롤해도 단어 쌍 목록이 표시됩니다.

Android

iOS

문제가 있나요?

앱이 올바르게 실행되지 않는 경우 다음 링크의 코드를 사용하면 제대로 작동할 수 있습니다.

축하합니다

이 Codelab의 1부를 완료했습니다. 앱을 확장하려면 2부로 진행하여 다음과 같이 앱을 수정합니다.

  • 상호작용을 추가합니다.
  • 새 경로를 탐색하는 기능을 추가합니다.
  • 테마 색상을 수정합니다.

2부를 완료하면 앱이 다음과 같이 표시됩니다.

7fcab088cd22cff7.gif

기타 다음 단계

다음 리소스를 통해 Flutter SDK에 관해 자세히 알아보세요.

다음과 같은 다른 리소스도 참고하세요.

또한 Flutter 커뮤니티와 소통해 보세요.