첫 Flutter 앱 작성, 2부

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

이 Codelab에서는 기본적인 모바일 Flutter 앱을 확장하여 상호작용을 추가합니다. 또한 사용자가 이동할 수 있는 두 번째 페이지(경로라고 함)도 만듭니다. 마지막으로 앱의 테마(색상)를 수정합니다. 이 Codelab은 지연 로드되는 무제한 목록을 만드는 1부와 이어지는 Codelab이지만, 2부에서 시작하는 개발자를 위해 시작 코드를 제공합니다.

2부에서 학습할 내용

  • iOS, Android, 웹에서 자연스럽게 보이는 Flutter 앱을 작성하는 방법
  • 핫 리로드를 사용한 개발 주기 단축 방법
  • 스테이트풀(Stateful) 위젯에 상호작용을 추가하는 방법
  • 두 번째 화면을 만들어 화면으로 이동하는 방법
  • 테마를 사용하여 앱의 모양을 변경하는 방법

2부에서 빌드할 항목

스타트업 회사를 위한 추천 이름 목록을 무제한으로 생성하는 간단한 모바일 앱으로 이 Codelab을 시작합니다. Codelab을 마치면 최종 사용자가 이름을 선택, 선택 해제하고 최적의 이름을 저장할 수 있게 됩니다. 앱 바의 오른쪽 상단에 있는 목록 아이콘을 탭하면 즐겨찾기에 추가된 이름만 나열된 새 페이지(경로라고 함)로 이동합니다.

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

7fcab088cd22cff7.gif

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

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

1부를 완료하지 않았다면 첫 Flutter 앱 작성, 1부Flutter 환경 설정을 참고하여 Flutter 개발을 위한 환경을 설정하세요.

이 Codelab의 1부를 완료했다면 이미 시작 앱인 startup_namer가 있습니다. 다음 단계로 이동하세요.

startup_namer가 없어도 걱정할 필요가 없습니다. 다음 안내에 따라 시작 앱을 가져올 수 있습니다.

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에서 모든 코드를 삭제합니다. 이 파일의 코드로 바꿉니다. 그러면 스타트업을 위한 추천 이름의 지연 로드 목록이 무제한으로 표시됩니다.

b2f84ff91b0e1396.png 영어 단어 패키지를 추가하여 pubspec.yaml을 업데이트합니다.

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  english_words: ^4.0.0-0    // NEW

영어 단어 패키지는 스타트업의 잠재적인 이름으로 사용되는 임의의 단어 쌍을 생성합니다.

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

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

b2f84ff91b0e1396.png 앱을 실행합니다.

원하는 만큼 스크롤하여 스타트업을 위한 추천 이름이 지속적으로 공급되는 것을 확인합니다.

이 단계에서는 각 행에 하트 아이콘을 추가합니다. 다음 단계에서는 하트 아이콘을 탭할 수 있게 만들고 즐겨찾기를 저장합니다.

b2f84ff91b0e1396.png _RandomWordsState_saved Set를 추가합니다. 이 Set에는 사용자가 즐겨찾기에 추가한 단어 쌍이 저장됩니다. 올바르게 구현된 Set는 중복 항목을 허용하지 않으므로 List보다 Set를 사용합니다.

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

b2f84ff91b0e1396.png _buildRow 함수에 alreadySaved 확인을 추가하여 단어 쌍이 이미 즐겨찾기에 추가되어 있지 않은지 확인합니다.

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);  // NEW
  ...
}

_buildRow()에서 ListTile 객체에 하트 모양의 아이콘을 추가하여 즐겨찾기를 사용 설정할 수도 있습니다. 다음 단계에서는 하트 아이콘을 사용해 상호작용하는 기능을 추가합니다.

b2f84ff91b0e1396.png 아래와 같이 텍스트 다음에 아이콘을 추가합니다.

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: Icon(   // NEW from here...
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),                // ... to here.
  );
}

b2f84ff91b0e1396.png 앱을 핫 리로드합니다.

이제 각 행에 열려 있는 하트가 표시되지만 아직 아이콘과 상호작용할 수는 없습니다.

Android

iOS

문제가 있나요?

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

이 단계에서는 하트 아이콘을 탭할 수 있게 만듭니다. 사용자가 목록의 항목을 탭하여 즐겨찾기 상태를 전환하면 이 단어 쌍이 일련의 저장된 즐겨찾기에서 삭제되거나 추가됩니다.

이렇게 하려면 _buildRow 함수를 수정합니다. 단어가 이미 즐겨찾기에 추가되어 있는 경우 단어를 다시 탭하면 즐겨찾기에서 삭제됩니다. 타일을 탭하면 함수는 setState()를 호출하여 상태가 변경되었음을 프레임워크에 알립니다.

b2f84ff91b0e1396.png 아래와 같이 _buildRow 메서드에 onTap을 추가합니다.

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

b2f84ff91b0e1396.png앱을 핫 리로드합니다.

타일을 탭하여 항목을 즐겨찾기에 추가하거나 즐겨찾기에서 삭제할 수 있습니다. 타일을 탭하면 탭 지점에서 시작되는 암시적 잉크 번짐 애니메이션이 생성됩니다.

Android

iOS

문제가 있나요?

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

이 단계에서는 즐겨찾기를 표시하는 새 페이지(Flutter에서는 경로라고 함)를 추가합니다. 홈 경로와 새로운 경로 간에 탐색하는 방법을 알아봅니다.

Flutter에서 Navigator는 앱의 경로가 포함된 스택을 관리합니다. 경로를 Navigator의 스택에 푸시하면 디스플레이가 이 경로로 업데이트됩니다. Navigator의 스택에서 경로를 표시하면 디스플레이가 이전 경로로 반환됩니다.

다음으로, _RandomWordsStatebuild 메서드에서 AppBar에 목록 아이콘을 추가합니다. 사용자가 목록 아이콘을 클릭하면 저장된 즐겨찾기가 포함된 새 경로가 Navigator에 푸시되고 아이콘이 표시됩니다.

b2f84ff91b0e1396.png build 메서드에 아이콘과 그에 해당하는 작업을 추가합니다.

class _RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
        actions: [
          IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

b2f84ff91b0e1396.png_RandomWordsState 클래스에 _pushSaved() 함수를 추가합니다.

  void _pushSaved() {
  }

b2f84ff91b0e1396.png 앱을 핫 리로드합니다. 목록 아이콘 a114478ae13b853.png이 앱 바에 나타납니다. _pushSaved 함수가 비어 있기 때문에 아이콘을 탭해도 아직 아무런 동작도 일어나지 않습니다.

다음으로, 경로를 빌드하여 Navigator의 스택에 푸시합니다. 이 작업을 하면 화면이 변경되어 새 경로가 표시됩니다. 새 페이지의 콘텐츠는 익명 함수 내 MaterialPageRoutebuilder 속성에 내장되어 있습니다.

b2f84ff91b0e1396.png 아래와 같이 Navigator.push를 호출하여 경로를 탐색기의 스택에 푸시합니다. IDE에서 유효하지 않은 코드를 경고하지만 다음 섹션에서 코드를 수정할 것입니다.

void _pushSaved() {
  Navigator.of(context).push(
  );
}

다음으로, MaterialPageRoute 및 빌더를 추가합니다. 지금은 ListTile 행을 생성하는 코드를 추가합니다. ListTiledivideTiles() 메서드는 각 ListTile 사이에 가로 간격을 추가합니다. divided 변수는 편의 함수 toList()가 목록으로 변환한 최종 행을 보유합니다.

b2f84ff91b0e1396.png다음 코드 스니펫과 같이 코드를 추가합니다.

  void _pushSaved() {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        // NEW lines from here...
        builder: (BuildContext context) {
          final tiles = _saved.map(
            (WordPair pair) {
              return ListTile(
                title: Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();

          return Scaffold(
            appBar: AppBar(
              title: Text('Saved Suggestions'),
            ),
            body: ListView(children: divided),
          );
        }, // ...to here.
      ),
    );
  }

builder 속성은 SavedSuggestions라는 새 경로의 앱 바가 포함된 Scaffold를 반환합니다. 새 경로의 본문은 ListTiles 행을 포함하는 ListView로 구성됩니다. 각 행은 구분선으로 구분됩니다.

b2f84ff91b0e1396.png 앱을 핫 리로드합니다. 선택한 항목 일부를 즐겨찾기에 추가하고 앱 바에서 목록 아이콘을 탭합니다. 즐겨찾기가 포함된 새 경로가 표시됩니다. 탐색기가 앱 바에 '뒤로' 버튼을 추가합니다. Navigator.pop을 명시적으로 구현할 필요가 없었습니다. '뒤로' 버튼을 탭하면 홈 경로로 돌아갑니다.

iOS - 기본 경로

iOS - 저장된 추천 경로

문제가 있나요?

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

이 단계에서는 앱 테마를 수정합니다. 테마는 앱의 디자인을 제어합니다. 실제 기기 또는 에뮬레이터에 따라 기본 테마를 사용하거나 브랜딩을 반영하도록 테마를 맞춤설정할 수 있습니다.

ThemeData 클래스를 구성하여 앱의 테마를 손쉽게 변경할 수 있습니다. 앱에서 기본 테마가 사용되지만 앱의 기본 색상을 흰색으로 변경하겠습니다.

b2f84ff91b0e1396.png MyApp 클래스에서 색상을 변경합니다.

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      theme: ThemeData(          // Add the 3 lines from here...
        primaryColor: Colors.white,
      ),                         // ... to here.
      home: RandomWords(),
    );
  }
}

b2f84ff91b0e1396.png 앱을 핫 리로드합니다. 이제 앱 바도 포함하여 전체 배경이 흰색으로 표시됩니다.

연습을 위해, ThemeData를 사용하여 UI의 다른 측면을 변경합니다. 머티리얼 라이브러리의 Colors 클래스는 사용해 볼 수 있는 다양한 색상 상수를 제공합니다. 핫 리로드를 사용하면 빠르고 쉽게 UI를 실험해 볼 수 있습니다.

Android

iOS

문제가 있나요?

올바르게 실행되지 않는 경우 다음 링크의 코드를 사용하여 최종 앱의 코드를 확인하세요.

다음 과정을 통해 iOS 및 Android에서 실행되는 대화형 Flutter 앱을 작성했습니다.

  • Dart 코드 작성
  • 핫 리로드를 사용한 개발 주기 단축
  • 스테이트풀(Stateful) 위젯 구현, 앱에 상호작용 추가
  • 경로를 만들고 홈 경로와 새로운 경로 간의 이동을 위한 로직 추가
  • 테마를 사용하여 앱 UI의 디자인을 변경하는 방법 학습

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

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

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