Firebase 에뮬레이터 도구 모음을 사용한 Flutter 앱 로컬 개발

1. 시작하기 전에

이 Codelab에서는 로컬 개발 중에 Flutter와 함께 Firebase 에뮬레이터 도구 모음을 사용하는 방법을 알아봅니다. 에뮬레이터 도구 모음을 통해 이메일-비밀번호 인증을 사용하는 방법과 Firestore 에뮬레이터에서 데이터를 읽고 쓰는 방법을 알아봅니다. 마지막으로 에뮬레이터에서 데이터를 가져오고 내보내 개발로 돌아갈 때마다 동일한 가짜 데이터를 사용합니다.

기본 요건

이 Codelab에서는 Flutter를 사용한 경험이 있다고 가정합니다. 그렇지 않은 경우 먼저 기본사항을 학습하는 것이 좋습니다. 다음 링크가 도움이 됩니다.

Firebase 경험도 있어야 하지만 Flutter 프로젝트에 Firebase를 추가한 적이 없어도 괜찮습니다. Firebase Console을 잘 모르거나 Firebase를 처음 사용하는 경우 다음 링크를 먼저 참고하세요.

만들 항목

이 Codelab에서는 간단한 일기 쓰기 애플리케이션을 빌드하는 방법을 안내합니다. 애플리케이션에는 로그인 화면과 이전 일기 항목을 읽고 새 항목을 만들 수 있는 화면이 있습니다.

cd5c4753bbee8af.png 8cb4d21f656540bf.png

학습할 내용

Firebase 사용을 시작하는 방법과 Firebase 에뮬레이터 도구 모음을 Flutter 개발 워크플로에 통합하고 사용하는 방법을 알아봅니다. 다음 Firebase 주제를 다룹니다.

이러한 주제는 Firebase 에뮬레이터 도구 모음을 다루는 데 필요한 범위 내에서 다루어집니다. 이 Codelab에서는 Flutter 앱에 Firebase 프로젝트를 추가하고 Firebase 에뮬레이터 도구 모음을 사용하여 개발하는 데 중점을 둡니다. Firebase 인증 또는 Firestore에 대한 심층적인 논의는 없습니다. 이러한 주제에 익숙하지 않다면 Flutter용 Firebase 알아보기 Codelab부터 시작하는 것이 좋습니다.

필요한 사항

  • Flutter에 대한 실무 지식과 SDK 설치
  • Intellij JetBrains 또는 VS Code 텍스트 편집기
  • Google Chrome 브라우저 (또는 Flutter의 다른 기본 개발 타겟) 이 Codelab의 일부 터미널 명령어는 Chrome에서 앱을 실행한다고 가정합니다.

2. Firebase 프로젝트 만들기 및 설정

가장 먼저 해야 할 일은 Firebase 웹 콘솔에서 Firebase 프로젝트를 만드는 것입니다. 이 Codelab의 대부분은 로컬에서 실행되는 UI를 사용하는 에뮬레이터 도구 모음에 중점을 두지만 먼저 전체 Firebase 프로젝트를 설정해야 합니다.

Firebase 프로젝트 만들기

  1. Google 계정을 사용하여 Firebase Console에 로그인합니다.
  2. 버튼을 클릭하여 새 프로젝트를 만든 다음 프로젝트 이름 (예: Firebase-Flutter-Codelab)을 입력합니다.
  3. 계속을 클릭합니다.
  4. 메시지가 표시되면 Firebase 약관을 검토하고 이에 동의한 다음 계속을 클릭합니다.
  5. (선택사항) Firebase Console에서 AI 지원('Firebase의 Gemini'라고 함)을 사용 설정합니다.
  6. 이 Codelab에서는 Google 애널리틱스가 필요하지 않으므로 Google 애널리틱스 옵션을 사용 중지합니다.
  7. 프로젝트 만들기를 클릭하고 프로젝트가 프로비저닝될 때까지 기다린 다음 계속을 클릭합니다.

Firebase 프로젝트에 관해 자세히 알아보려면 Firebase 프로젝트 이해를 참고하세요.

Firebase 제품 설정

빌드 중인 앱에는 Flutter 앱에 사용할 수 있는 다음과 같은 두 가지 Firebase 제품이 사용됩니다.

  • Firebase 인증 - 사용자가 앱에 로그인할 수 있음
  • Cloud Firestore - 클라우드에 구조화된 데이터를 저장하고 데이터가 변경되면 인스턴트 알림을 받을 수 있음

이 두 제품은 특수 구성이 필요하거나 Firebase Console을 사용하여 사용 설정해야 합니다.

Cloud Firestore 사용 설정

Flutter 앱은 Cloud Firestore를 사용하여 일기 항목을 저장합니다.

Cloud Firestore를 사용 설정합니다.

  1. Firebase Console의 빌드 섹션에서 Cloud Firestore를 클릭합니다.
  2. 데이터베이스 만들기를 클릭합니다. 99e8429832d23fa3.png
  3. 테스트 모드에서 시작 옵션을 선택합니다. 보안 규칙에 대한 면책조항을 읽습니다. 테스트 모드를 사용하면 개발 중에 데이터베이스에 자유롭게 쓸 수 있습니다. 다음을 클릭합니다. 6be00e26c72ea032.png
  4. 데이터베이스의 위치를 선택합니다. 그냥 기본값을 사용할 수도 있습니다. 이 위치는 나중에 변경할 수 없습니다. 278656eefcfb0216.png
  5. 사용 설정을 클릭합니다.

3. Flutter 앱 설정

시작하기 전에 스타터 코드를 다운로드하고 Firebase CLI를 설치해야 합니다.

시작 코드 가져오기

명령줄에서 GitHub 저장소를 클론합니다.

git clone https://github.com/flutter/codelabs.git flutter-codelabs

또는 GitHub의 CLI 도구가 설치되어 있는 경우 다음을 실행합니다.

gh repo clone flutter/codelabs flutter-codelabs

샘플 코드를 Codelab 모음의 코드가 포함된 flutter-codelabs 디렉터리에 클론해야 합니다. 이 Codelab의 코드는 flutter-codelabs/firebase-emulator-suite에 있습니다.

flutter-codelabs/firebase-emulator-suite 아래의 디렉터리 구조는 두 개의 Flutter 프로젝트로 구성됩니다. 하나는 complete이며, 이를 참고하여 앞으로 건너뛰거나 자체 코드를 상호 참조할 수 있습니다. 다른 프로젝트는 start라고 합니다.

시작하려는 코드는 flutter-codelabs/firebase-emulator-suite/start 디렉터리에 있습니다. 해당 디렉터리를 열거나 원하는 IDE로 가져옵니다.

cd flutter-codelabs/firebase-emulator-suite/start

Firebase CLI 설치

Firebase CLI는 Firebase 프로젝트를 관리하는 도구를 제공합니다. 에뮬레이터 도구 모음을 사용하려면 CLI가 필요하므로 설치해야 합니다.

CLI를 설치하는 방법은 다양합니다. MacOS 또는 Linux를 사용하는 경우 가장 간단한 방법은 터미널에서 다음 명령어를 실행하는 것입니다.

curl -sL https://firebase.tools | bash

CLI를 설치한 후 Firebase로 인증해야 합니다.

  1. 다음 명령어를 실행하여 Google 계정으로 Firebase에 로그인합니다.
firebase login
  1. 이 명령어는 로컬 머신을 Firebase에 연결하고 Firebase 프로젝트에 대한 액세스 권한을 부여합니다.
  1. Firebase 프로젝트를 나열하여 CLI가 올바르게 설치되었고 사용자 계정에 액세스할 수 있는지 테스트합니다. 다음 명령어를 실행합니다.
firebase projects:list
  1. 표시된 목록은 Firebase Console에 나열된 Firebase 프로젝트와 같아야 합니다. firebase-flutter-codelab이 하나 이상 표시됩니다.

FlutterFire CLI 설치

FlutterFire CLI는 Firebase CLI를 기반으로 빌드되며 Firebase 프로젝트를 Flutter 앱과 더 쉽게 통합할 수 있습니다.

먼저 CLI를 설치합니다.

dart pub global activate flutterfire_cli

CLI가 설치되었는지 확인합니다. Flutter 프로젝트 디렉터리 내에서 다음 명령어를 실행하고 CLI가 도움말 메뉴를 출력하는지 확인합니다.

flutterfire --help

Firebase CLI 및 FlutterFire CLI를 사용하여 Firebase 프로젝트를 Flutter 앱에 추가

두 CLI를 설치하면 몇 개의 터미널 명령어로 개별 Firebase 제품 (예: Firestore)을 설정하고, 에뮬레이터를 다운로드하고, Flutter 앱에 Firebase를 추가할 수 있습니다.

먼저 다음을 실행하여 Firebase 설정을 완료합니다.

firebase init

이 명령어를 실행하면 프로젝트를 설정하는 데 필요한 일련의 질문이 표시됩니다. 다음 스크린샷은 흐름을 보여줍니다.

  1. 기능을 선택하라는 메시지가 표시되면 'Firestore'와 '에뮬레이터'를 선택합니다. (Flutter 프로젝트 파일에서 수정할 수 있는 구성을 사용하지 않으므로 인증 옵션이 없습니다.) fe6401d769be8f53.png
  2. 메시지가 표시되면 '기존 프로젝트 사용'을 선택합니다.

f11dcab439e6ac1e.png

  1. 이제 이전 단계에서 만든 프로젝트(flutter-firebase-codelab)를 선택합니다.

3bdc0c6934991c25.png

  1. 다음으로 생성될 파일의 이름을 지정하는 방법에 관한 일련의 질문을 받게 됩니다. 각 질문에서 'Enter' 키를 눌러 기본값을 선택하는 것이 좋습니다. 9bfa2d507e199c59.png
  2. 마지막으로 에뮬레이터를 구성해야 합니다. 목록에서 Firestore 및 인증을 선택한 다음 각 에뮬레이터에 사용할 특정 포트에 관한 각 질문에 'Enter' 키를 누릅니다. 에뮬레이터 UI를 사용할지 묻는 메시지가 표시되면 기본값인 '예'를 선택해야 합니다.

프로세스가 끝나면 다음 스크린샷과 같은 출력이 표시됩니다.

중요: 이미 에뮬레이터를 다운로드한 경우 최종 질문이 기본적으로 '아니요'로 설정되므로 아래 스크린샷에 표시된 것과 출력이 약간 다를 수 있습니다.

8544e41037637b07.png

FlutterFire 구성

그런 다음 FlutterFire를 사용하여 Flutter 앱에서 Firebase를 사용하는 데 필요한 Dart 코드를 생성할 수 있습니다.

flutterfire configure

이 명령어를 실행하면 사용할 Firebase 프로젝트와 설정할 플랫폼을 선택하라는 메시지가 표시됩니다. 이 Codelab에서는 예시로 Flutter Web을 사용하지만 모든 옵션을 사용하도록 Firebase 프로젝트를 설정할 수 있습니다.

다음 스크린샷은 답변해야 하는 프롬프트를 보여줍니다.

619b7aca6dc15472.png 301c9534f594f472.png

이 스크린샷은 프로세스 종료 시의 출력을 보여줍니다. Firebase에 익숙하다면 콘솔에서 애플리케이션을 만들지 않아도 되고 FlutterFire CLI가 대신 만들어 준다는 것을 알 수 있습니다.

12199a85ade30459.png

Flutter 앱에 Firebase 패키지 추가

마지막 설정 단계는 관련 Firebase 패키지를 Flutter 프로젝트에 추가하는 것입니다. 터미널에서 Flutter 프로젝트의 루트(flutter-codelabs/firebase-emulator-suite/start)에 있는지 확인합니다. 그런 다음 다음 세 명령어를 실행합니다.

flutter pub add firebase_core
flutter pub add firebase_auth
flutter pub add cloud_firestore

이 애플리케이션에서 사용할 패키지는 이것이 전부입니다.

4. Firebase 에뮬레이터 사용 설정

지금까지는 에뮬레이터를 사용할 수 있도록 Flutter 앱과 Firebase 프로젝트를 설정했지만, 여전히 Flutter 코드에 나가는 Firebase 요청을 로컬 포트로 리라우팅하도록 알려야 합니다.

먼저 main.dart.main 함수에 Firebase 초기화 코드와 에뮬레이터 설정 코드를 추가합니다.

main.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import 'app_state.dart';
import 'firebase_options.dart';
import 'logged_in_view.dart';
import 'logged_out_view.dart';


void main() async {
 WidgetsFlutterBinding.ensureInitialized();
 await Firebase.initializeApp(
   options: DefaultFirebaseOptions.currentPlatform,
 );

 if (kDebugMode) {
   try {
     FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080);
     await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
   } catch (e) {
     // ignore: avoid_print
     print(e);
   }
 }

 runApp(MyApp());
}

코드의 처음 몇 줄은 Firebase를 초기화합니다. Flutter 앱에서 Firebase를 사용하는 경우 거의 모든 상황에서 WidgetsFlutterBinding.ensureInitializedFirebase.initializeApp를 호출하여 시작하는 것이 좋습니다.

그 후 if (kDebugMode) 줄로 시작하는 코드는 프로덕션 Firebase 프로젝트가 아닌 에뮬레이터를 타겟팅하도록 앱에 지시합니다. kDebugMode는 개발 환경에 있는 경우에만 에뮬레이터를 타겟팅하도록 합니다. kDebugMode는 상수 값이므로 Dart 컴파일러는 출시 모드에서 해당 코드 블록을 완전히 삭제해야 한다는 것을 알고 있습니다.

에뮬레이터 시작

Flutter 앱을 시작하기 전에 에뮬레이터를 시작해야 합니다. 먼저 터미널에서 다음을 실행하여 에뮬레이터를 시작합니다.

firebase emulators:start

이 명령어는 에뮬레이터를 부팅하고 에뮬레이터와 상호작용할 수 있는 localhost 포트를 노출합니다. 이 명령어를 실행하면 다음과 비슷한 출력이 표시됩니다.

bb7181eb70829606.png

이 출력은 실행 중인 에뮬레이터와 에뮬레이터를 볼 수 있는 위치를 알려줍니다. 먼저 localhost:4000에서 에뮬레이터 UI를 확인합니다.

11563f4c7216de81.png

로컬 에뮬레이터 UI의 홈페이지입니다. 사용 가능한 모든 에뮬레이터가 나열되며 각 에뮬레이터에는 상태(사용 또는 사용 중지)가 표시됩니다.

5. Firebase 인증 에뮬레이터

사용할 첫 번째 에뮬레이터는 인증 에뮬레이터입니다. UI의 인증 카드에서 '에뮬레이터로 이동'을 클릭하여 인증 에뮬레이터를 시작하면 다음과 같은 페이지가 표시됩니다.

3c1bfded40733189.png

이 페이지는 인증 웹 콘솔 페이지와 유사합니다. 온라인 콘솔과 같이 사용자를 나열하는 표가 있으며 사용자를 수동으로 추가할 수 있습니다. 여기서 큰 차이점은 에뮬레이터에서 사용할 수 있는 유일한 인증 방법 옵션이 이메일 및 비밀번호를 통한 방법이라는 것입니다. 이는 로컬 개발에 충분합니다.

다음으로 Firebase 인증 에뮬레이터에 사용자를 추가한 다음 Flutter UI를 통해 해당 사용자를 로그인하는 과정을 살펴봅니다.

사용자 추가하기

'사용자 추가' 버튼을 클릭하고 다음 정보를 사용하여 양식을 작성합니다.

  • 표시 이름: 대시
  • 이메일: dash@email.com
  • 비밀번호: dashword

양식을 제출하면 이제 표에 사용자가 포함됩니다. 이제 해당 사용자로 로그인하도록 코드를 업데이트할 수 있습니다.

logged_out_view.dart

업데이트해야 하는 LoggedOutView 위젯의 코드는 사용자가 로그인 버튼을 누를 때 트리거되는 콜백에만 있습니다. 코드를 다음과 같이 업데이트합니다.

class LoggedOutView extends StatelessWidget {
 final AppState state;
 const LoggedOutView({super.key, required this.state});
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Firebase Emulator Suite Codelab'),
     ),
     body: Center(
       child: Column(
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
          Text(
           'Please log in',
            style: Theme.of(context).textTheme.displaySmall,
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: ElevatedButton(
             onPressed: () async {
              await state.logIn('dash@email.com', 'dashword').then((_) {
                if (state.user != null) {
                 context.go('/');
                }
              });
              },
              child: const Text('Log In'),
          ),
        ),
      ],
    ),
   ),
  );
 }
}

업데이트된 코드는 TODO 문자열을 인증 에뮬레이터에서 만든 이메일과 비밀번호로 대체합니다. 다음 줄에서는 if(true) 줄이 state.user이 null인지 확인하는 코드로 대체되었습니다. AppClass의 코드를 보면 이 내용을 더 자세히 알 수 있습니다.

app_state.dart

AppState의 코드 두 부분을 업데이트해야 합니다. 먼저 클래스 멤버 AppState.user에 Object 유형이 아닌 firebase_auth 패키지의 User 유형을 부여합니다.

두 번째로 아래와 같이 AppState.login 메서드를 작성합니다.

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'entry.dart';

class AppState {
 AppState() {
   _entriesStreamController = StreamController.broadcast(onListen: () {
     _entriesStreamController.add([
       Entry(
         date: '10/09/2022',
         text: lorem,
         title: '[Example] My Journal Entry',
       )
     ]);
   });
 }

 User? user; // <-- changed variable type
 Stream<List<Entry>> get entries => _entriesStreamController.stream;
 late final StreamController<List<Entry>> _entriesStreamController;

 Future<void> logIn(String email, String password) async {
   final credential = await FirebaseAuth.instance
       .signInWithEmailAndPassword(email: email, password: password);
   if (credential.user != null) {
     user = credential.user!;
     _listenForEntries();
   } else {
     print('no user!');
   }
 } 
 // ...
}

이제 사용자의 유형 정의는 User?입니다. 이 User 클래스는 Firebase Auth에서 제공되며, 잠시 후에 설명할 User.displayName와 같은 필요한 정보를 제공합니다.

Firebase 인증에서 이메일과 비밀번호로 사용자를 로그인하는 데 필요한 기본 코드입니다. FirebaseAuth를 호출하여 로그인하고 Future<UserCredential> 객체를 반환합니다. 퓨처가 완료되면 이 코드는 UserCredential에 연결된 User가 있는지 확인합니다. 사용자 인증 정보 객체에 사용자가 있으면 사용자가 로그인한 것이므로 AppState.user 속성을 설정할 수 있습니다. 없으면 오류가 발생한 것이므로 오류가 출력됩니다.

이 메서드에서 이 앱에만 해당하는 코드 줄 (일반 FirebaseAuth 코드 아님)은 _listenForEntries 메서드 호출뿐이며, 이는 다음 단계에서 다룹니다.

TODO: 작업 아이콘 - 앱을 새로고침한 다음 렌더링되면 로그인 버튼을 누릅니다. 이렇게 하면 앱이 상단에 '다시 오셨습니다, 사용자님!'이라고 표시된 페이지로 이동합니다. 인증은 이 페이지로 이동할 수 있으므로 작동해야 하지만 사용자의 실제 이름을 표시하려면 logged_in_view.dart를 약간 업데이트해야 합니다.

logged_in_view.dart

LoggedInView.build 메서드의 첫 번째 줄을 다음으로 변경합니다.

class LoggedInView extends StatelessWidget {
 final AppState state;
 LoggedInView({super.key, required this.state});

 final PageController _controller = PageController(initialPage: 1);

 @override
 Widget build(BuildContext context) {
   final name = state.user!.displayName ?? 'No Name';

   return Scaffold(
 // ...

이제 이 줄은 AppState 객체의 User 속성에서 displayName를 가져옵니다. 이 displayName는 첫 번째 사용자를 정의할 때 에뮬레이터에서 설정되었습니다. 이제 로그인하면 TODO 대신 '다시 오셨군요, 대시님!'이 표시됩니다.

6. Firestore 에뮬레이터에 데이터 읽기 및 쓰기

먼저 Firestore 에뮬레이터를 확인하세요. 에뮬레이터 UI 홈페이지 (localhost:4000)에서 Firestore 카드에 있는 '에뮬레이터로 이동'을 클릭합니다. 예를 들면 다음과 같습니다.

에뮬레이터:

791fce7dc137910a.png

Firebase Console:

e0dde9aea34af050.png

Firestore를 사용해 본 적이 있다면 이 페이지가 Firebase Console Firestore 페이지와 유사하다는 것을 알 수 있습니다. 하지만 몇 가지 중요한 차이점이 있습니다.

  1. 버튼 한 번 탭으로 모든 데이터를 삭제할 수 있습니다. 프로덕션 데이터에는 위험하지만 빠른 반복에는 유용합니다. 새 프로젝트를 진행 중이고 데이터 모델이 변경되는 경우 쉽게 정리할 수 있습니다.
  2. '요청' 탭이 있습니다. 이 탭을 사용하면 이 에뮬레이터에 대한 수신 요청을 확인할 수 있습니다. 이 탭에 대해서는 잠시 후에 자세히 설명하겠습니다.
  3. 규칙, 색인 또는 사용량 탭이 없습니다. 보안 규칙을 작성하는 데 도움이 되는 도구가 있지만 (다음 섹션에서 설명) 로컬 에뮬레이터의 보안 규칙은 설정할 수 없습니다.

이 목록을 요약하면 이 버전의 Firestore는 개발 중에 유용한 도구를 더 많이 제공하고 프로덕션에 필요한 도구를 삭제합니다.

Firestore에 쓰기

에뮬레이터의 '요청' 탭을 살펴보기 전에 먼저 요청을 합니다. 이를 위해서는 코드를 업데이트해야 합니다. 먼저 앱에서 양식을 연결하여 새 일기 Entry를 Firestore에 작성합니다.

Entry를 제출하는 대략적인 흐름은 다음과 같습니다.

  1. 사용자가 양식을 작성하고 Submit 버튼을 눌렀습니다.
  2. UI가 AppState.writeEntryToFirebase를 호출합니다.
  3. AppState.writeEntryToFirebase는 Firebase에 항목을 추가합니다.

1단계 또는 2단계와 관련된 코드는 변경할 필요가 없습니다. 3단계에 추가해야 하는 코드는 AppState 클래스에 추가됩니다. AppState.writeEntryToFirebase을 다음과 같이 변경합니다.

app_state.dart

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'entry.dart';

class AppState {
 AppState() {
   _entriesStreamController = StreamController.broadcast(onListen: () {
     _entriesStreamController.add([
       Entry(
         date: '10/09/2022',
         text: lorem,
         title: '[Example] My Journal Entry',
       )
     ]);
   });
 }

 User? user;
 Stream<List<Entry>> get entries => _entriesStreamController.stream;
 late final StreamController<List<Entry>> _entriesStreamController;

 Future<void> logIn(String email, String password) async {
   final credential = await FirebaseAuth.instance
       .signInWithEmailAndPassword(email: email, password: password);
   if (credential.user != null) {
     user = credential.user!;
     _listenForEntries();
   } else {
     print('no user!');
   }
 }

 void writeEntryToFirebase(Entry entry) {
   FirebaseFirestore.instance.collection('Entries').add(<String, String>{
     'title': entry.title,
     'date': entry.date.toString(),
     'text': entry.text,
   });
 }
 // ...
}

writeEntryToFirebase 메서드의 코드는 Firestore에서 'Entries'라는 컬렉션에 대한 참조를 가져옵니다. 그런 다음 Map<String, String> 유형이어야 하는 새 항목을 추가합니다.

이 경우 Firestore에 'Entries' 컬렉션이 없었으므로 Firestore에서 컬렉션을 만들었습니다.

코드를 추가한 후 앱을 핫 리로드하거나 다시 시작하고 로그인한 다음 EntryForm 뷰로 이동합니다. 원하는 Strings을 사용하여 양식을 작성하면 됩니다. 날짜 필드는 이 Codelab에서 간소화되었으므로 모든 문자열을 사용할 수 있습니다. 강력한 유효성 검사가 없으며 DateTime 객체를 어떤 방식으로든 신경 쓰지 않습니다.)

양식에서 제출을 누릅니다. 앱에서는 아무 일도 일어나지 않지만 에뮬레이터 UI에서 새 항목을 확인할 수 있습니다.

Firestore 에뮬레이터의 요청 탭

UI에서 Firestore 에뮬레이터로 이동하여 '데이터' 탭을 확인합니다. 이제 데이터베이스의 루트에 'Entries'라는 컬렉션이 표시됩니다. 양식에 입력한 정보와 동일한 정보가 포함된 문서가 있어야 합니다.

a978fb34fb8a83da.png

AppState.writeEntryToFirestore가 작동했음을 확인했으므로 이제 요청 탭에서 요청을 자세히 살펴볼 수 있습니다. 지금 탭을 클릭합니다.

Firestore 에뮬레이터 요청

다음과 비슷한 목록이 표시됩니다.

f0b37f0341639035.png

이러한 목록 항목을 클릭하면 유용한 정보를 많이 확인할 수 있습니다. 새 일지 항목을 만들라는 요청에 해당하는 CREATE 목록 항목을 클릭합니다. 다음과 같은 새 표가 표시됩니다.

385d62152e99aad4.png

앞서 언급한 것처럼 Firestore 에뮬레이터는 앱의 보안 규칙을 개발하는 도구를 제공합니다. 이 뷰에는 요청이 통과한 보안 규칙의 정확한 줄이 표시됩니다 (실패한 경우 실패한 줄이 표시됨). 더 강력한 앱에서는 보안 규칙이 늘어나 여러 승인 확인이 있을 수 있습니다. 이 뷰는 이러한 승인 규칙을 작성하고 디버그하는 데 사용됩니다.

또한 메타데이터와 인증 데이터를 비롯한 이 요청의 모든 부분을 쉽게 검사할 수 있습니다. 이 데이터는 복잡한 승인 규칙을 작성하는 데 사용됩니다.

Firestore에서 읽기

Firestore는 데이터 동기화를 사용하여 업데이트된 데이터를 연결된 기기에 푸시합니다. Flutter 코드에서 Firestore 컬렉션과 문서를 수신 대기 (또는 구독)할 수 있으며 데이터가 변경될 때마다 코드에 알림이 전송됩니다. 이 앱에서는 AppState._listenForEntries라는 메서드에서 Firestore 업데이트를 수신 대기합니다.

이 코드는 각각 AppState._entriesStreamControllerAppState.entries라고 하는 StreamControllerStream와 함께 작동합니다. 이 코드는 이미 작성되어 있으며 Firestore의 데이터를 표시하는 데 필요한 모든 코드도 UI에 작성되어 있습니다.

아래 코드와 일치하도록 _listenForEntries 메서드를 업데이트합니다.

app_state.dart

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'entry.dart';

class AppState {
 AppState() {
   _entriesStreamController = StreamController.broadcast(onListen: () {
     _entriesStreamController.add([
       Entry(
         date: '10/09/2022',
         text: lorem,
         title: '[Example] My Journal Entry',
       )
     ]);
   });
 }

 User? user;
 Stream<List<Entry>> get entries => _entriesStreamController.stream;
 late final StreamController<List<Entry>> _entriesStreamController;

 Future<void> logIn(String email, String password) async {
   final credential = await FirebaseAuth.instance
       .signInWithEmailAndPassword(email: email, password: password);
   if (credential.user != null) {
     user = credential.user!;
     _listenForEntries();
   } else {
     print('no user!');
   }
 }

 void writeEntryToFirebase(Entry entry) {
   FirebaseFirestore.instance.collection('Entries').add(<String, String>{
     'title': entry.title,
     'date': entry.date.toString(),
     'text': entry.text,
   });
 }

 void _listenForEntries() {
   FirebaseFirestore.instance
       .collection('Entries')
       .snapshots()
       .listen((event) {
     final entries = event.docs.map((doc) {
       final data = doc.data();
       return Entry(
         date: data['date'] as String,
         text: data['text'] as String,
         title: data['title'] as String,
       );
     }).toList();

     _entriesStreamController.add(entries);
   });
 }
 // ...
}

이 코드는 Firestore의 'Entries' 컬렉션을 리슨합니다. Firestore가 이 클라이언트에 새 데이터가 있다고 알리면 해당 데이터가 전달되고 _listenForEntries의 코드는 모든 하위 문서를 앱에서 사용할 수 있는 객체 (Entry)로 변경합니다. 그런 다음 이러한 항목을 _entriesStreamController라는 StreamController에 추가합니다 (UI가 수신 대기 중). 이 코드는 필수 업데이트입니다.

마지막으로 AppState.logIn 메서드는 _listenForEntries를 호출하며, 이는 사용자가 로그인한 후 리스닝 프로세스를 시작합니다.

// ...
Future<void> logIn(String email, String password) async {
 final credential = await FirebaseAuth.instance
     .signInWithEmailAndPassword(email: email, password: password);
 if (credential.user != null) {
   user = credential.user!;
   _listenForEntries();
 } else {
   print('no user!');
 }
}
// ...

이제 앱을 실행합니다. 다음과 같이 표시됩니다.

b8a31c7a8900331.gif

7. 에뮬레이터로 데이터 내보내기 및 가져오기

Firebase 에뮬레이터는 데이터 가져오기 및 내보내기를 지원합니다. 가져오기 및 내보내기를 사용하면 개발을 중단했다가 다시 시작할 때 동일한 데이터로 개발을 계속할 수 있습니다. 데이터 파일을 Git에 커밋할 수도 있으며, 함께 작업하는 다른 개발자도 동일한 데이터를 사용하여 작업할 수 있습니다.

에뮬레이터 데이터 내보내기

먼저 이미 보유한 에뮬레이터 데이터를 내보냅니다. 에뮬레이터가 계속 실행되는 동안 새 터미널 창을 열고 다음 명령어를 입력합니다.

firebase emulators:export ./emulators_data

.emulators_data는 Firebase에 데이터를 내보낼 위치를 알려주는 인수입니다. 디렉터리가 없으면 생성됩니다. 해당 디렉터리에 원하는 이름을 사용할 수 있습니다.

이 명령어를 실행하면 명령어를 실행한 터미널에 다음과 같은 출력이 표시됩니다.

i  Found running emulator hub for project flutter-firebase-codelab-d6b79 at http://localhost:4400
i  Creating export directory /Users/ewindmill/Repos/codelabs/firebase-emulator-suite/complete/emulators_data
i  Exporting data to: /Users/ewindmill/Repos/codelabs/firebase-emulator-suite/complete/emulators_data
✔  Export complete

에뮬레이터가 실행 중인 터미널 창으로 전환하면 다음과 같은 출력이 표시됩니다.

i  emulators: Received export request. Exporting data to /Users/ewindmill/Repos/codelabs/firebase-emulator-suite/complete/emulators_data.
✔  emulators: Export complete.

마지막으로 프로젝트 디렉터리를 살펴보면 저장한 데이터가 포함된 JSON 파일이 다른 메타데이터 파일과 함께 포함된 ./emulators_data 디렉터리가 표시됩니다.

에뮬레이터 데이터 가져오기

이제 개발 워크플로의 일부로 해당 데이터를 가져와서 중단한 부분부터 시작할 수 있습니다.

먼저 터미널에서 CTRL+C를 눌러 에뮬레이터가 실행 중인 경우 중지합니다.

그런 다음 이미 살펴본 emulators:start 명령어를 실행하되 가져올 데이터를 지정하는 플래그를 사용합니다.

firebase emulators:start --import ./emulators_data

에뮬레이터가 실행되면 localhost:4000에서 에뮬레이터 UI로 이동하고 이전에 사용한 것과 동일한 데이터가 표시됩니다.

에뮬레이터를 닫을 때 데이터 자동 내보내기

매 개발 세션이 끝날 때마다 데이터를 내보내는 것을 기억하는 대신 에뮬레이터를 종료할 때 데이터를 자동으로 내보낼 수도 있습니다.

에뮬레이터를 시작할 때 두 개의 추가 플래그와 함께 emulators:start 명령어를 실행합니다.

firebase emulators:start --import ./emulators_data --export-on-exit

완료되었습니다. 이제 이 프로젝트의 에뮬레이터로 작업할 때마다 데이터가 저장되고 다시 로드됩니다. –export-on-exit flag에 다른 디렉터리를 인수로 지정할 수도 있지만 –import에 전달된 디렉터리가 기본값으로 사용됩니다.

이 옵션의 조합도 사용할 수 있습니다. 문서의 참고사항: 내보내기 디렉터리는 firebase emulators:start --export-on-exit=./saved-data 플래그로 지정할 수 있습니다. --import를 사용하면 내보내기 경로의 기본값이 동일하게 설정됩니다(예: firebase emulators:start --import=./data-path --export-on-exit). 마지막으로 원하는 경우 다른 디렉터리 경로를 --import--export-on-exit 플래그에 전달합니다.

8. 수고하셨습니다.

Firebase 에뮬레이터와 Flutter로 시작하기를 완료했습니다. 이 Codelab의 완료된 코드는 GitHub의 'complete' 디렉터리(Flutter Codelab)에서 확인할 수 있습니다.

학습한 내용

  • Firebase를 사용하도록 Flutter 앱 설정
  • Firebase 프로젝트 설정
  • FlutterFire CLI
  • Firebase CLI
  • Firebase 인증 에뮬레이터
  • Firebase Firestore 에뮬레이터
  • 에뮬레이터 데이터 가져오기 및 내보내기

다음 단계

자세히 알아보기

스파키가 너를 자랑스러워해!

2a0ad195769368b1.gif